@ethora/chat-component-rn 26.5.3 → 26.5.5
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/CHANGELOG.md +28 -0
- package/README.md +25 -4
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +1 -7
- package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +56 -4
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js +1 -38
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +20 -28
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
- package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
- package/lib/commonjs/components/UsersList/Checkbox.js +68 -0
- package/lib/commonjs/components/UsersList/Checkbox.js.map +1 -0
- package/lib/commonjs/components/UsersList/StyledComponents.js +1 -7
- package/lib/commonjs/components/UsersList/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/UsersList/UsersList.js +3 -1
- package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +14 -0
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/module/components/ContextMenu/ContextMenuComponents.js +0 -6
- package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +58 -6
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js +3 -40
- package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +21 -29
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js +9 -6
- package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
- package/lib/module/components/UsersList/Checkbox.js +63 -0
- package/lib/module/components/UsersList/Checkbox.js.map +1 -0
- package/lib/module/components/UsersList/StyledComponents.js +0 -6
- package/lib/module/components/UsersList/StyledComponents.js.map +1 -1
- package/lib/module/components/UsersList/UsersList.js +2 -1
- package/lib/module/components/UsersList/UsersList.js.map +1 -1
- package/lib/module/context/xmppProvider.js +14 -0
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts +0 -1
- package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts +8 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts.map +1 -1
- package/lib/typescript/components/UsersList/Checkbox.d.ts +16 -0
- package/lib/typescript/components/UsersList/Checkbox.d.ts.map +1 -0
- package/lib/typescript/components/UsersList/StyledComponents.d.ts +0 -1
- package/lib/typescript/components/UsersList/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/metro.js +17 -57
- package/package.json +1 -1
- package/src/components/ContextMenu/ContextMenuComponents.tsx +0 -7
- package/src/components/MainComponents/ChatWrapper.tsx +56 -4
- package/src/components/MessageBubble/MessageInteractions.tsx +2 -105
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +20 -41
- package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +9 -7
- package/src/components/UsersList/Checkbox.tsx +68 -0
- package/src/components/UsersList/StyledComponents.ts +0 -8
- package/src/components/UsersList/UsersList.tsx +1 -1
- package/src/context/xmppProvider.tsx +14 -0
- package/lib/commonjs/components/InputComponents/AudioInput.js +0 -23
- package/lib/commonjs/components/InputComponents/AudioInput.js.map +0 -1
- package/lib/commonjs/components/InputComponents/AudioRecorder.js +0 -101
- package/lib/commonjs/components/InputComponents/AudioRecorder.js.map +0 -1
- package/lib/commonjs/components/InputComponents/MediaInput.js +0 -98
- package/lib/commonjs/components/InputComponents/MediaInput.js.map +0 -1
- package/lib/commonjs/components/InputComponents/RecordingIndicator.js +0 -70
- package/lib/commonjs/components/InputComponents/RecordingIndicator.js.map +0 -1
- package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -207
- package/lib/commonjs/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
- package/lib/commonjs/components/OperationalModal/OperationalModal.js +0 -83
- package/lib/commonjs/components/OperationalModal/OperationalModal.js.map +0 -1
- package/lib/module/components/InputComponents/AudioInput.js +0 -18
- package/lib/module/components/InputComponents/AudioInput.js.map +0 -1
- package/lib/module/components/InputComponents/AudioRecorder.js +0 -95
- package/lib/module/components/InputComponents/AudioRecorder.js.map +0 -1
- package/lib/module/components/InputComponents/MediaInput.js +0 -92
- package/lib/module/components/InputComponents/MediaInput.js.map +0 -1
- package/lib/module/components/InputComponents/RecordingIndicator.js +0 -65
- package/lib/module/components/InputComponents/RecordingIndicator.js.map +0 -1
- package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js +0 -200
- package/lib/module/components/Modals/ModalSelectMedia/ModalSelectMedia.js.map +0 -1
- package/lib/module/components/OperationalModal/OperationalModal.js +0 -77
- package/lib/module/components/OperationalModal/OperationalModal.js.map +0 -1
- package/lib/typescript/components/InputComponents/AudioInput.d.ts +0 -9
- package/lib/typescript/components/InputComponents/AudioInput.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/AudioRecorder.d.ts +0 -9
- package/lib/typescript/components/InputComponents/AudioRecorder.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/MediaInput.d.ts +0 -16
- package/lib/typescript/components/InputComponents/MediaInput.d.ts.map +0 -1
- package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts +0 -4
- package/lib/typescript/components/InputComponents/RecordingIndicator.d.ts.map +0 -1
- package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts +0 -9
- package/lib/typescript/components/Modals/ModalSelectMedia/ModalSelectMedia.d.ts.map +0 -1
- package/lib/typescript/components/OperationalModal/OperationalModal.d.ts +0 -9
- package/lib/typescript/components/OperationalModal/OperationalModal.d.ts.map +0 -1
- package/src/components/InputComponents/AudioInput.tsx +0 -24
- package/src/components/InputComponents/AudioRecorder.tsx +0 -100
- package/src/components/InputComponents/MediaInput.tsx +0 -113
- package/src/components/InputComponents/RecordingIndicator.tsx +0 -77
- package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +0 -283
- package/src/components/OperationalModal/OperationalModal.tsx +0 -78
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
RecordContainer,
|
|
4
|
-
Timer,
|
|
5
|
-
} from '../styled/StyledInputComponents/StyledInputComponents';
|
|
6
|
-
import { RecordIcon, RemoveIcon, SendIcon } from '../../assets/icons';
|
|
7
|
-
import Button from '../styled/Button';
|
|
8
|
-
import RecordingIndicator from './RecordingIndicator';
|
|
9
|
-
import { Platform, View } from 'react-native';
|
|
10
|
-
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
|
|
11
|
-
import RNFS from 'react-native-fs';
|
|
12
|
-
|
|
13
|
-
interface AudioRecorderProps {
|
|
14
|
-
setIsRecording: (state: boolean) => void;
|
|
15
|
-
isRecording: boolean;
|
|
16
|
-
handleSendClick: (audioBlob?: any) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const AudioRecorder: React.FC<AudioRecorderProps> = ({
|
|
20
|
-
setIsRecording,
|
|
21
|
-
isRecording,
|
|
22
|
-
handleSendClick,
|
|
23
|
-
}) => {
|
|
24
|
-
const audioRecorderPlayer = useRef(new AudioRecorderPlayer()).current;
|
|
25
|
-
const [timer, setTimer] = useState('00:00');
|
|
26
|
-
const [audioPath, setAudioPath] = useState<string | null>(null);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
return () => {
|
|
30
|
-
audioRecorderPlayer.stopRecorder();
|
|
31
|
-
audioRecorderPlayer.removeRecordBackListener();
|
|
32
|
-
};
|
|
33
|
-
}, [audioRecorderPlayer]);
|
|
34
|
-
|
|
35
|
-
const startRecording = async () => {
|
|
36
|
-
try {
|
|
37
|
-
const path = Platform.select({
|
|
38
|
-
ios: `${RNFS.DocumentDirectoryPath}/recording.m4a`,
|
|
39
|
-
android: `${RNFS.CachesDirectoryPath}/recording.mp4`,
|
|
40
|
-
});
|
|
41
|
-
const uri = await audioRecorderPlayer.startRecorder(path);
|
|
42
|
-
setAudioPath(uri);
|
|
43
|
-
|
|
44
|
-
audioRecorderPlayer.addRecordBackListener((e: any) => {
|
|
45
|
-
const minutes = Math.floor(e.currentPosition / 60000);
|
|
46
|
-
const seconds = Math.floor((e.currentPosition % 60000) / 1000);
|
|
47
|
-
setTimer(
|
|
48
|
-
`${minutes.toString().padStart(2, '0')}:${seconds
|
|
49
|
-
.toString()
|
|
50
|
-
.padStart(2, '0')}`
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
setIsRecording(true);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('Error starting recording:', error);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const stopRecording = async () => {
|
|
61
|
-
try {
|
|
62
|
-
await audioRecorderPlayer.stopRecorder();
|
|
63
|
-
audioRecorderPlayer.removeRecordBackListener();
|
|
64
|
-
setIsRecording(false);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('Error stopping recording:', error);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const sendAudio = () => {
|
|
71
|
-
if (audioPath) {
|
|
72
|
-
console.log('Audio sent:', audioPath);
|
|
73
|
-
handleSendClick(audioPath);
|
|
74
|
-
resetState();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const resetState = () => {
|
|
79
|
-
setAudioPath(null);
|
|
80
|
-
setTimer('00:00');
|
|
81
|
-
setIsRecording(false);
|
|
82
|
-
};
|
|
83
|
-
return isRecording ? (
|
|
84
|
-
<RecordContainer>
|
|
85
|
-
<View style={{ display: 'flex', alignItems: 'center' }}>
|
|
86
|
-
{isRecording && <RecordingIndicator />}
|
|
87
|
-
<Timer>{timer}</Timer>
|
|
88
|
-
</View>
|
|
89
|
-
|
|
90
|
-
<View style={{ display: 'flex', gap: 8 }}>
|
|
91
|
-
<Button onPress={stopRecording} EndIcon={<RemoveIcon />} unstyled />
|
|
92
|
-
<Button onPress={sendAudio} EndIcon={<SendIcon />} unstyled />
|
|
93
|
-
</View>
|
|
94
|
-
</RecordContainer>
|
|
95
|
-
) : (
|
|
96
|
-
<Button onPress={startRecording} EndIcon={<RecordIcon />} />
|
|
97
|
-
);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export default AudioRecorder;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
2
|
-
import { View, Image, TouchableOpacity } from 'react-native';
|
|
3
|
-
import { Video, ResizeMode } from 'expo-av';
|
|
4
|
-
import * as ImagePicker from 'expo-image-picker';
|
|
5
|
-
import { AttachIcon, RemoveIcon } from '../../assets/icons';
|
|
6
|
-
import Button from '../styled/Button';
|
|
7
|
-
import { IConfig } from '../../types/types';
|
|
8
|
-
|
|
9
|
-
interface FilePreview {
|
|
10
|
-
uri: string;
|
|
11
|
-
name: string;
|
|
12
|
-
type: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface MediaInputProps {
|
|
16
|
-
filePreviews: FilePreview[];
|
|
17
|
-
setFilePreviews: (
|
|
18
|
-
update: FilePreview[] | ((prev: FilePreview[]) => FilePreview[])
|
|
19
|
-
) => void;
|
|
20
|
-
handleSendClick: () => void;
|
|
21
|
-
config?: IConfig;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const MediaInput: React.FC<MediaInputProps> = ({
|
|
25
|
-
filePreviews,
|
|
26
|
-
setFilePreviews,
|
|
27
|
-
handleSendClick,
|
|
28
|
-
config,
|
|
29
|
-
}) => {
|
|
30
|
-
const handleAttachClick = useCallback(async () => {
|
|
31
|
-
const result = await ImagePicker.launchImageLibraryAsync({
|
|
32
|
-
mediaTypes: ['images', 'videos'],
|
|
33
|
-
allowsMultipleSelection: true,
|
|
34
|
-
selectionLimit: 5,
|
|
35
|
-
quality: 0.8,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
if (!result.canceled && result.assets) {
|
|
39
|
-
const newFiles = result.assets.map((asset) => ({
|
|
40
|
-
uri: asset.uri,
|
|
41
|
-
name: asset.fileName || asset.uri.split('/').pop() || `file_${Date.now()}`,
|
|
42
|
-
type: asset.mimeType || 'application/octet-stream',
|
|
43
|
-
}));
|
|
44
|
-
setFilePreviews((prevFiles: FilePreview[]) =>
|
|
45
|
-
[...prevFiles, ...newFiles].slice(0, 5)
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}, [setFilePreviews]);
|
|
49
|
-
|
|
50
|
-
const handleRemoveFile = useCallback(
|
|
51
|
-
(file: FilePreview) => {
|
|
52
|
-
setFilePreviews((prevFiles: FilePreview[]) =>
|
|
53
|
-
prevFiles.filter((f) => f.uri !== file.uri)
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
[setFilePreviews]
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const renderFilePreview = useCallback((file: FilePreview) => {
|
|
60
|
-
if (file.type?.startsWith('image/')) {
|
|
61
|
-
return (
|
|
62
|
-
<Image source={{ uri: file.uri }} style={{ width: 50, height: 50 }} />
|
|
63
|
-
);
|
|
64
|
-
} else if (file.type?.startsWith('video/')) {
|
|
65
|
-
return (
|
|
66
|
-
<Video
|
|
67
|
-
source={{ uri: file.uri }}
|
|
68
|
-
style={{ width: 50, height: 50 }}
|
|
69
|
-
useNativeControls
|
|
70
|
-
resizeMode={ResizeMode.COVER}
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<View>
|
|
79
|
-
<Button
|
|
80
|
-
onPress={handleAttachClick}
|
|
81
|
-
disabled={config?.disableMedia}
|
|
82
|
-
EndIcon={<AttachIcon />}
|
|
83
|
-
/>
|
|
84
|
-
{filePreviews.length > 0 && (
|
|
85
|
-
<View>
|
|
86
|
-
{filePreviews.map((file) => (
|
|
87
|
-
<View key={file.uri} style={{ position: 'relative', margin: 8 }}>
|
|
88
|
-
{renderFilePreview(file)}
|
|
89
|
-
<TouchableOpacity
|
|
90
|
-
onPress={() => handleRemoveFile(file)}
|
|
91
|
-
style={{
|
|
92
|
-
position: 'absolute',
|
|
93
|
-
top: 4,
|
|
94
|
-
right: 4,
|
|
95
|
-
width: 20,
|
|
96
|
-
height: 20,
|
|
97
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
98
|
-
justifyContent: 'center',
|
|
99
|
-
alignItems: 'center',
|
|
100
|
-
borderRadius: 10,
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<RemoveIcon />
|
|
104
|
-
</TouchableOpacity>
|
|
105
|
-
</View>
|
|
106
|
-
))}
|
|
107
|
-
</View>
|
|
108
|
-
)}
|
|
109
|
-
</View>
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export default MediaInput;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import React, {useEffect, useRef} from 'react';
|
|
2
|
-
import {View, StyleSheet, Animated} from 'react-native';
|
|
3
|
-
|
|
4
|
-
const RecordingIndicator = () => {
|
|
5
|
-
const scaleAnim = useRef(new Animated.Value(1)).current; // For scaling the circle
|
|
6
|
-
const opacityAnim = useRef(new Animated.Value(1)).current; // For pulsating effect
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
Animated.loop(
|
|
10
|
-
Animated.sequence([
|
|
11
|
-
Animated.parallel([
|
|
12
|
-
Animated.timing(scaleAnim, {
|
|
13
|
-
toValue: 1.3,
|
|
14
|
-
duration: 750,
|
|
15
|
-
useNativeDriver: true,
|
|
16
|
-
}),
|
|
17
|
-
Animated.timing(opacityAnim, {
|
|
18
|
-
toValue: 0.6,
|
|
19
|
-
duration: 750,
|
|
20
|
-
useNativeDriver: true,
|
|
21
|
-
}),
|
|
22
|
-
]),
|
|
23
|
-
Animated.parallel([
|
|
24
|
-
Animated.timing(scaleAnim, {
|
|
25
|
-
toValue: 1,
|
|
26
|
-
duration: 750,
|
|
27
|
-
useNativeDriver: true,
|
|
28
|
-
}),
|
|
29
|
-
Animated.timing(opacityAnim, {
|
|
30
|
-
toValue: 1,
|
|
31
|
-
duration: 750,
|
|
32
|
-
useNativeDriver: true,
|
|
33
|
-
}),
|
|
34
|
-
]),
|
|
35
|
-
]),
|
|
36
|
-
).start();
|
|
37
|
-
}, [scaleAnim, opacityAnim]);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<View style={styles.wrapper}>
|
|
41
|
-
<Animated.View
|
|
42
|
-
style={[
|
|
43
|
-
styles.outerCircle,
|
|
44
|
-
{
|
|
45
|
-
transform: [{scale: scaleAnim}],
|
|
46
|
-
opacity: opacityAnim,
|
|
47
|
-
},
|
|
48
|
-
]}
|
|
49
|
-
/>
|
|
50
|
-
<View style={styles.innerCircle} />
|
|
51
|
-
</View>
|
|
52
|
-
);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const styles = StyleSheet.create({
|
|
56
|
-
wrapper: {
|
|
57
|
-
justifyContent: 'center',
|
|
58
|
-
alignItems: 'center',
|
|
59
|
-
marginRight: 10,
|
|
60
|
-
},
|
|
61
|
-
outerCircle: {
|
|
62
|
-
width: 18,
|
|
63
|
-
height: 18,
|
|
64
|
-
borderRadius: 9,
|
|
65
|
-
backgroundColor: 'rgba(255, 0, 0, 0.6)',
|
|
66
|
-
position: 'absolute',
|
|
67
|
-
},
|
|
68
|
-
innerCircle: {
|
|
69
|
-
width: 16,
|
|
70
|
-
height: 16,
|
|
71
|
-
borderRadius: 8,
|
|
72
|
-
backgroundColor: 'red',
|
|
73
|
-
position: 'relative',
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export default RecordingIndicator;
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import React, { FC, useMemo } from 'react';
|
|
2
|
-
import { Alert, Linking, Platform } from 'react-native';
|
|
3
|
-
import Button from '../../styled/Button';
|
|
4
|
-
import DropdownMenu from '../../DropdownMenu/DropdownMenu';
|
|
5
|
-
import DocumentPicker from 'react-native-document-picker';
|
|
6
|
-
import ImagePicker from 'react-native-image-crop-picker';
|
|
7
|
-
import {
|
|
8
|
-
check,
|
|
9
|
-
request,
|
|
10
|
-
PERMISSIONS,
|
|
11
|
-
RESULTS,
|
|
12
|
-
Permission,
|
|
13
|
-
} from 'react-native-permissions';
|
|
14
|
-
import {
|
|
15
|
-
AttachIcon,
|
|
16
|
-
CameraIcon,
|
|
17
|
-
DocumentIcon,
|
|
18
|
-
MediaIcon,
|
|
19
|
-
} from '../../../assets/icons';
|
|
20
|
-
import { MediaFile } from '../../../types/types';
|
|
21
|
-
|
|
22
|
-
interface ModalSelectMediaProps {
|
|
23
|
-
onFileSelect: (files: MediaFile[]) => void;
|
|
24
|
-
config?: any;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface Permissions {
|
|
28
|
-
UNAVAILABLE: 'unavailable';
|
|
29
|
-
BLOCKED: 'blocked';
|
|
30
|
-
DENIED: 'denied';
|
|
31
|
-
GRANTED: 'granted';
|
|
32
|
-
LIMITED: 'limited';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const ModalSelectMedia: FC<ModalSelectMediaProps> = ({
|
|
36
|
-
onFileSelect,
|
|
37
|
-
config,
|
|
38
|
-
}) => {
|
|
39
|
-
const checkPermission = async (permission: Permission) => {
|
|
40
|
-
const status = await check(permission);
|
|
41
|
-
|
|
42
|
-
if (status === RESULTS.GRANTED) {
|
|
43
|
-
return status;
|
|
44
|
-
} else if (status === RESULTS.DENIED) {
|
|
45
|
-
const requestStatus = await request(permission);
|
|
46
|
-
if (requestStatus === RESULTS.GRANTED) {
|
|
47
|
-
return requestStatus;
|
|
48
|
-
} else if (requestStatus === RESULTS.DENIED) {
|
|
49
|
-
return requestStatus;
|
|
50
|
-
}
|
|
51
|
-
} else if (status === RESULTS.UNAVAILABLE) {
|
|
52
|
-
return status;
|
|
53
|
-
} else {
|
|
54
|
-
return status;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const handleCameraSelection = async () => {
|
|
59
|
-
const permission =
|
|
60
|
-
Platform.OS === 'ios'
|
|
61
|
-
? PERMISSIONS.IOS.CAMERA
|
|
62
|
-
: PERMISSIONS.ANDROID.CAMERA;
|
|
63
|
-
const permissionStatus = await checkPermission(permission);
|
|
64
|
-
|
|
65
|
-
if (permissionStatus !== RESULTS.GRANTED) {
|
|
66
|
-
Alert.alert(
|
|
67
|
-
'Permission required',
|
|
68
|
-
'Camera permission is needed to take photos.',
|
|
69
|
-
[
|
|
70
|
-
{
|
|
71
|
-
text: 'Cancel',
|
|
72
|
-
onPress: () => console.log('Camera permission cancelled'),
|
|
73
|
-
style: 'cancel',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
text: 'Open Settings',
|
|
77
|
-
onPress: () => Linking.openSettings(),
|
|
78
|
-
},
|
|
79
|
-
]
|
|
80
|
-
);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const image = await ImagePicker.openCamera({
|
|
86
|
-
width: 300,
|
|
87
|
-
height: 400,
|
|
88
|
-
cropping: true,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const originalName = image.path.split('/').pop();
|
|
92
|
-
const file = {
|
|
93
|
-
uri: image.path,
|
|
94
|
-
type: image.mime,
|
|
95
|
-
name: originalName || `camera_${Date.now()}.jpg`,
|
|
96
|
-
};
|
|
97
|
-
onFileSelect([file]);
|
|
98
|
-
} catch (error: any) {
|
|
99
|
-
const errorMessage = error?.message || String(error);
|
|
100
|
-
const isSimulatorError =
|
|
101
|
-
errorMessage.includes('simulator') ||
|
|
102
|
-
errorMessage.includes('Cannot run camera') ||
|
|
103
|
-
errorMessage.includes('Camera not available');
|
|
104
|
-
|
|
105
|
-
if (isSimulatorError) {
|
|
106
|
-
Alert.alert(
|
|
107
|
-
'Camera unavailable',
|
|
108
|
-
'Camera is not available on iOS Simulator. Please test on a real device.',
|
|
109
|
-
[{ text: 'OK' }]
|
|
110
|
-
);
|
|
111
|
-
} else if (error?.code !== 'E_PICKER_CANCELLED') {
|
|
112
|
-
console.error('Camera error:', error);
|
|
113
|
-
Alert.alert(
|
|
114
|
-
'Camera error',
|
|
115
|
-
errorMessage || 'Failed to open camera. Please try again.',
|
|
116
|
-
[{ text: 'OK' }]
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const handleGallerySelection = async () => {
|
|
123
|
-
try {
|
|
124
|
-
let permission: Permission;
|
|
125
|
-
|
|
126
|
-
if (Platform.OS === 'ios') {
|
|
127
|
-
permission = PERMISSIONS.IOS.PHOTO_LIBRARY;
|
|
128
|
-
} else if (Number(Platform.Version) >= 33) {
|
|
129
|
-
permission = PERMISSIONS.ANDROID.READ_MEDIA_IMAGES;
|
|
130
|
-
} else {
|
|
131
|
-
permission = PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log('handleGallerySelection - checking permission:', permission);
|
|
135
|
-
const permissionStatus = await checkPermission(permission);
|
|
136
|
-
console.log('handleGallerySelection - permission status:', permissionStatus);
|
|
137
|
-
|
|
138
|
-
if (permissionStatus !== RESULTS.GRANTED) {
|
|
139
|
-
console.log('handleGallerySelection - permission not granted, showing alert');
|
|
140
|
-
Alert.alert(
|
|
141
|
-
'Permission required',
|
|
142
|
-
'Gallery permission is needed to select photos.',
|
|
143
|
-
[
|
|
144
|
-
{
|
|
145
|
-
text: 'Cancel',
|
|
146
|
-
onPress: () => console.log('Gallery permission cancelled'),
|
|
147
|
-
style: 'cancel',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
text: 'Open Settings',
|
|
151
|
-
onPress: () => Linking.openSettings(),
|
|
152
|
-
},
|
|
153
|
-
]
|
|
154
|
-
);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
159
|
-
const image = await ImagePicker.openPicker({
|
|
160
|
-
multiple: false,
|
|
161
|
-
mediaType: 'any',
|
|
162
|
-
});
|
|
163
|
-
console.log('handleGallerySelection - image selected:', image);
|
|
164
|
-
|
|
165
|
-
const originalName = image.path.split('/').pop();
|
|
166
|
-
const file = {
|
|
167
|
-
uri: image.path,
|
|
168
|
-
type: image.mime,
|
|
169
|
-
name:
|
|
170
|
-
originalName ||
|
|
171
|
-
`gallery_${Date.now()}${
|
|
172
|
-
image.mime.includes('video') ? '.mp4' : '.jpg'
|
|
173
|
-
}`,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
onFileSelect([file]);
|
|
177
|
-
} catch (error: any) {
|
|
178
|
-
if (error?.code === 'E_PICKER_CANCELLED') {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
console.error('Gallery error:', error);
|
|
183
|
-
console.error('Gallery error code:', error?.code);
|
|
184
|
-
console.error('Gallery error message:', error?.message);
|
|
185
|
-
|
|
186
|
-
const errorMessage = error?.message || String(error);
|
|
187
|
-
Alert.alert(
|
|
188
|
-
'Gallery error',
|
|
189
|
-
errorMessage || 'Failed to open gallery. Please try again.',
|
|
190
|
-
[{ text: 'OK' }]
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const handleFileSelection = async () => {
|
|
196
|
-
try {
|
|
197
|
-
const result = await DocumentPicker.pick({
|
|
198
|
-
type: [DocumentPicker.types.allFiles],
|
|
199
|
-
allowMultiSelection: false,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const files = result.map((file: any) => {
|
|
203
|
-
const originalName = file.name;
|
|
204
|
-
return {
|
|
205
|
-
uri: file.uri,
|
|
206
|
-
type: file.type || 'unknown',
|
|
207
|
-
name: originalName || `file_${Date.now()}`,
|
|
208
|
-
};
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
onFileSelect(files);
|
|
212
|
-
} catch (err: any) {
|
|
213
|
-
if (DocumentPicker.isCancel(err)) {
|
|
214
|
-
console.log('User cancelled file picker');
|
|
215
|
-
} else {
|
|
216
|
-
console.error('DocumentPicker Error:', err);
|
|
217
|
-
const errorMessage = err?.message || String(err);
|
|
218
|
-
Alert.alert(
|
|
219
|
-
'File selection error',
|
|
220
|
-
errorMessage || 'Failed to select file. Please try again.',
|
|
221
|
-
[{ text: 'OK' }]
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const menuOptions = useMemo(
|
|
228
|
-
() => [
|
|
229
|
-
{
|
|
230
|
-
label: 'Camera',
|
|
231
|
-
icon: <CameraIcon />,
|
|
232
|
-
onClick: async () => {
|
|
233
|
-
setTimeout(async () => {
|
|
234
|
-
await handleCameraSelection();
|
|
235
|
-
}, 500);
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
label: 'Media File',
|
|
240
|
-
icon: <MediaIcon />,
|
|
241
|
-
onClick: async () => {
|
|
242
|
-
await handleGallerySelection();
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
label: 'Document',
|
|
247
|
-
icon: <DocumentIcon />,
|
|
248
|
-
onClick: async () => {
|
|
249
|
-
setTimeout(async () => {
|
|
250
|
-
await handleFileSelection();
|
|
251
|
-
}, 500);
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
],
|
|
255
|
-
[]
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
return (
|
|
259
|
-
<DropdownMenu
|
|
260
|
-
position="leftBottom"
|
|
261
|
-
options={menuOptions}
|
|
262
|
-
openButton={(onPress) => {
|
|
263
|
-
return (
|
|
264
|
-
<Button
|
|
265
|
-
style={{
|
|
266
|
-
padding: 0,
|
|
267
|
-
height: 40,
|
|
268
|
-
width: 40,
|
|
269
|
-
justifyContent: 'center',
|
|
270
|
-
alignItems: 'center',
|
|
271
|
-
backgroundColor: 'transparent',
|
|
272
|
-
margin: 0,
|
|
273
|
-
}}
|
|
274
|
-
EndIcon={<AttachIcon color={config?.colors?.primary || '#0052CD'} />}
|
|
275
|
-
onPress={() => {
|
|
276
|
-
onPress();
|
|
277
|
-
}}
|
|
278
|
-
/>
|
|
279
|
-
);
|
|
280
|
-
}}
|
|
281
|
-
/>
|
|
282
|
-
);
|
|
283
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
// import QRCode from 'react-qr-code';
|
|
3
|
-
import { CloseButton } from '../Modals/styledModalComponents';
|
|
4
|
-
import { Overlay, StyledModal } from '../styled/MediaModal';
|
|
5
|
-
import { StyledInput } from '../styled/StyledInputComponents/StyledInputComponents';
|
|
6
|
-
import Button from '../styled/Button';
|
|
7
|
-
import { Text, View } from 'react-native';
|
|
8
|
-
import { QRCODE_URL } from '../../helpers/constants/PLATFORM_CONSTANTS';
|
|
9
|
-
import Clipboard from '@react-native-clipboard/clipboard';
|
|
10
|
-
|
|
11
|
-
interface OperationalModalProps {
|
|
12
|
-
isVisible: boolean;
|
|
13
|
-
chatJid: string;
|
|
14
|
-
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const OperationalModal: React.FC<OperationalModalProps> = ({
|
|
18
|
-
isVisible,
|
|
19
|
-
chatJid,
|
|
20
|
-
setVisible,
|
|
21
|
-
}) => {
|
|
22
|
-
const handleCopyClick = () => {
|
|
23
|
-
Clipboard.setString(`${QRCODE_URL}${chatJid}`);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
isVisible && (
|
|
28
|
-
<Overlay
|
|
29
|
-
style={{
|
|
30
|
-
position: 'absolute',
|
|
31
|
-
}}
|
|
32
|
-
>
|
|
33
|
-
<StyledModal
|
|
34
|
-
style={{
|
|
35
|
-
borderRadius: 16,
|
|
36
|
-
width: 'auto',
|
|
37
|
-
height: 'auto',
|
|
38
|
-
paddingHorizontal: 64,
|
|
39
|
-
paddingVertical: 32,
|
|
40
|
-
minWidth: 480,
|
|
41
|
-
}}
|
|
42
|
-
>
|
|
43
|
-
<CloseButton onPress={() => setVisible(false)}>
|
|
44
|
-
<Text style={{ fontSize: 24 }}>×</Text>
|
|
45
|
-
</CloseButton>
|
|
46
|
-
<View
|
|
47
|
-
style={{
|
|
48
|
-
display: 'flex',
|
|
49
|
-
flexDirection: 'column',
|
|
50
|
-
gap: 16,
|
|
51
|
-
alignItems: 'center',
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
<View style={{ width: '70%', position: 'relative' }} />
|
|
55
|
-
|
|
56
|
-
<View
|
|
57
|
-
style={{
|
|
58
|
-
display: 'flex',
|
|
59
|
-
gap: 8,
|
|
60
|
-
alignItems: 'center',
|
|
61
|
-
minWidth: 400,
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
<StyledInput
|
|
65
|
-
value={chatJid}
|
|
66
|
-
editable={true}
|
|
67
|
-
style={{ width: '80%' }}
|
|
68
|
-
/>
|
|
69
|
-
<Button text="Copy" onPress={handleCopyClick} />
|
|
70
|
-
</View>
|
|
71
|
-
</View>
|
|
72
|
-
</StyledModal>
|
|
73
|
-
</Overlay>
|
|
74
|
-
)
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export default OperationalModal;
|