Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,31 @@ Then using our Addons component in ChatScreen
```javascript
import React from 'react'
import {ChatScreen as BaseChatScreen} from 'rn-firebase-chat'
import {CameraView, useCamera} from 'rn-firebase-chat/src/addons/camera'
import {
CameraView,
useCamera,
FileAttachmentRef,
FileAttachment,
} from 'rn-firebase-chat/src/addons';

...

export const ChatScreen: React.FC = () => {
const {onPressCamera, onPressGallery} = useCamera()
const fileAttachmentRef = useRef<FileAttachmentRef>(null);
const renderLeftCustomView = useCallback(() => {
return (
<View>
<Button
title="file"
onPress={() => {
fileAttachmentRef.current?.pickDocument();
}}
/>
</View>
);
}, []);

return (
<BaseChatScreen
memberIds={[partnerInfo.id]}
Expand All @@ -127,9 +146,17 @@ export const ChatScreen: React.FC = () => {
hasGallery: true,
onPressCamera,
onPressGallery,
renderLeftCustomView
}}
>
{({onSend}) => (<CameraView onSend={onSend} /> )}
{({onSend}) => (
<View>
{/* FileAttachment component */}
<FileAttachment ref={fileAttachmentRef} onSend={onSend} />
{/* CameraView component */}
<CameraView onSend={onSend} />
</View>
)}
</BaseChatScreen>
)
}
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@
"react-native-video": "^6.2.0",
"react-native-vision-camera": "^4.1.0",
"release-it": "^15.0.0",
"typescript": "^4.5.2",
"uuid": "^10.0.0"
"typescript": "^4.5.2"
},
"resolutions": {
"@types/react-native": "0.71.8",
Expand Down Expand Up @@ -160,6 +159,9 @@
"react-native-gifted-chat": "^2.6.3",
"react-native-image-picker": "^7.1.2",
"react-native-reanimated": "^3.15.3",
"react-native-safe-area-context": "^4.11.0"
"react-native-safe-area-context": "^4.11.0",
"react-native-document-picker": "^9.3.1",
"react-native-fs": "^2.20.0",
"react-native-uuid": "^2.0.2"
}
}
4 changes: 2 additions & 2 deletions src/addons/camera/CameraView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { Camera, useCameraDevice } from 'react-native-vision-camera';
import type { PhotoFile, VideoFile } from 'react-native-vision-camera';
import Video from 'react-native-video';
import { v4 as uuidv4 } from 'uuid';
import uuid from 'react-native-uuid';
import { CaptureCameraButton } from './CaptureButton';
import type {
CameraViewMethods,
Expand Down Expand Up @@ -90,7 +90,7 @@ export const CameraView: React.FC<CameraViewProps> = ({

const onSendPressed = useCallback(async () => {
const extension = getMediaTypeFromExtension(media.path);
const id = uuidv4();
const id = uuid.v4();
const message = {
id: id,
_id: id,
Expand Down
119 changes: 119 additions & 0 deletions src/addons/fileAttachment/FileAttachment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { forwardRef, useCallback, useImperativeHandle } from 'react';
import { Platform, PermissionsAndroid } from 'react-native';
import DocumentPicker, {
type DocumentPickerResponse,
} from 'react-native-document-picker';
import uuid from 'react-native-uuid';
import { formatSize, getAbsoluteFilePathWithName } from '../../utilities';
import { type MediaType, type MessageProps } from '../../interfaces';
import { useChatContext } from '../../hooks';

interface FileAttachmentProps {
onSend: (message: MessageProps) => void;
}

export interface FileAttachmentRef {
pickDocument: () => Promise<void>;
}

const FileAttachment = forwardRef<FileAttachmentRef, FileAttachmentProps>(
({ onSend }, ref) => {
const { userInfo } = useChatContext();

const checkPermissionAndroid = async () => {
if (
Platform.OS === 'android' &&
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Write External Storage Permission',
message: 'This app needs access to your storage to download files.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
console.log('WRITE_EXTERNAL_STORAGE permission denied');
return false;
}
return true;
} else {
return true;
}
};

const onSendMessage = useCallback(
async (media: DocumentPickerResponse) => {
try {
const extension = media?.name?.split('.').pop() ?? 'pdf';
const type = 'document' as MediaType;
const id = uuid.v4();
const user = {
_id: userInfo?.id || '',
...userInfo,
};
const message = {
id: id,
_id: id,
text: '',
type: type,
path: await getAbsoluteFilePathWithName(
media.uri,
media.name ?? ''
),
extension,
size: formatSize(media.size),
fileName: media.name || '',
user: user,
} as MessageProps;
onSend(message);
} catch (error) {
console.log('error: ', error);
}
},
[onSend, userInfo]
);

const handleDocumentPick = useCallback(async () => {
if (!(await checkPermissionAndroid())) return;

try {
const result = await DocumentPicker.pick({
type: [
DocumentPicker.types.pdf,
DocumentPicker.types.zip,
DocumentPicker.types.doc,
DocumentPicker.types.docx,
DocumentPicker.types.pptx,
DocumentPicker.types.ppt,
DocumentPicker.types.xls,
DocumentPicker.types.xlsx,
],
});
const media = result?.[0];
if (media) {
onSendMessage(media);
} else {
console.log('Something went wrong. Please try again.');
}
} catch (err) {
if (DocumentPicker.isCancel(err)) {
console.log('Document Picker was cancelled');
} else {
throw err;
}
}
}, [onSendMessage]);

useImperativeHandle(ref, () => ({
pickDocument: handleDocumentPick,
}));

return null;
}
);

export default FileAttachment;
3 changes: 3 additions & 0 deletions src/addons/fileAttachment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FileAttachment, { FileAttachmentRef } from './FileAttachment';

export { FileAttachment, FileAttachmentRef };
2 changes: 2 additions & 0 deletions src/addons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './camera';
export * from './fileAttachment';
1 change: 1 addition & 0 deletions src/asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Images = {
placeHolder: require('../images/place_holder.png'),
playIcon: require('../images/play.png'),
pauseWhiteIcon: require('../images/pause_white.png'),
document: require('../images/document.png'),
};

export default Images;
2 changes: 2 additions & 0 deletions src/chat/components/ConversationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const ConversationItem: React.FC<IConversationItemProps> = ({
return `${senderInfo} Video`;
case MessageTypes.voice:
return `${senderInfo} Voice`;
case MessageTypes.document:
return `${senderInfo} Document`;
default:
return '';
}
Expand Down
20 changes: 19 additions & 1 deletion src/chat/components/bubble/CustomBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CustomImageVideoBubbleProps,
} from './CustomImageVideoBubble';
import MessageStatus from '../MessageStatus';
import { CustomDocumentBubble } from './CustomDocumentBubble';

interface CustomBubbleProps {
bubbleMessage: Bubble<MessageProps>['props'];
Expand Down Expand Up @@ -94,7 +95,24 @@ export const CustomBubble: React.FC<CustomBubbleProps> = ({
{ViewMessageStatus}
</View>
);

case MessageTypes.document:
return (
<View>
<Bubble
{...bubbleMessage}
renderCustomView={() =>
currentMessage && (
<CustomDocumentBubble
message={currentMessage}
position={position}
/>
)
}
wrapperStyle={styleBuble}
/>
{ViewMessageStatus}
</View>
);
default: {
return (
<View>
Expand Down
126 changes: 126 additions & 0 deletions src/chat/components/bubble/CustomDocumentBubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useCallback } from 'react';
import {
StyleSheet,
View,
Pressable,
Image,
ViewStyle,
StyleProp,
ImageStyle,
Text,
TextProps,
Share,
} from 'react-native';
import type { MessageProps } from '../../../interfaces';
import Images from '../../../asset';

export interface CustomDocumentBubbleProps {
message: MessageProps;
position: 'left' | 'right';
icon?: string;
bubbleContainerStyle?: StyleProp<ViewStyle>;
buttonStyle?: StyleProp<ViewStyle>;
iconStyle?: StyleProp<ImageStyle>;
textStyle?: StyleProp<TextProps>;
textSizeStyle?: StyleProp<TextProps>;
doucmentStyle?: StyleProp<ViewStyle>;

renderCustomDocument?: (
message: MessageProps,
position: 'left' | 'right'
) => React.ReactNode;
}

export const CustomDocumentBubble: React.FC<CustomDocumentBubbleProps> = ({
position,
message,
icon = Images.document,
iconStyle,
bubbleContainerStyle,
buttonStyle,
doucmentStyle,
textStyle,
textSizeStyle,
renderCustomDocument,
}) => {
const handleDocumentsPress = useCallback(async () => {
if (!message || !message.path) return;
try {
await Share.share({
url: message.path,
title: message.fileName,
message: message.fileName,
});
} catch (err) {
console.log('Error, Failed to download document');
}
}, [message]);

const renderDocument = () => {
return (
<View style={[styles.wrapper, doucmentStyle]}>
<Image source={icon} style={[styles.icon, iconStyle]} />
<View style={[styles.groupText]}>
<Text style={[styles.text, textStyle]}>{message.fileName}</Text>
<Text style={[styles.size, textSizeStyle]}>{message.size}</Text>
</View>
</View>
);
};

if (renderCustomDocument) {
renderCustomDocument(message, position);
}

return (
<View
style={[
styles.bubbleContainer,
bubbleContainerStyle,
position === 'left' ? styles.flexStart : styles.flexEnd,
]}
>
<Pressable onPress={handleDocumentsPress} style={buttonStyle}>
{renderDocument()}
</Pressable>
</View>
);
};

const styles = StyleSheet.create({
bubbleContainer: {
paddingHorizontal: 5,
marginTop: 5,
flex: 1,
},
flexEnd: {
justifyContent: 'flex-end',
},
flexStart: {
justifyContent: 'flex-start',
},
icon: {
width: 35,
height: 35,
},
wrapper: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
backgroundColor: '#e0f7fa',
borderRadius: 10,
},
groupText: {
marginLeft: 10,
},
text: {
fontSize: 15,
color: 'black',
fontWeight: 'bold',
},
size: {
fontSize: 13,
color: 'gray',
marginTop: 5,
},
});
Binary file added src/images/document.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading