Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b4a394a
chore: adding react-native-file-viewer library
AlexAlexandre Oct 27, 2021
27e4ab1
feat: create a util file with download functions and file views
AlexAlexandre Oct 27, 2021
c631493
refactor: removing unnecessary field from IAttachment interface
AlexAlexandre Oct 28, 2021
299b203
feat: adding a loading when click a file from reply message
AlexAlexandre Oct 28, 2021
6c36321
refactor: minor tweak
AlexAlexandre Oct 28, 2021
19794c3
refactor: add config for download on Android
AlexAlexandre Oct 28, 2021
81a8c3c
refactor: minor tweak
AlexAlexandre Oct 28, 2021
d5c07dc
feat: create some util functions for download
AlexAlexandre Oct 28, 2021
7f7dc3a
feat: force download unsupported videos
AlexAlexandre Oct 28, 2021
20b9a1a
minor tweak
AlexAlexandre Oct 28, 2021
0a4e4f7
Merge branch 'develop' into feat/download-file
AlexAlexandre Oct 29, 2021
0befc81
refactor: minor tweak
AlexAlexandre Nov 3, 2021
7c7e20d
refactor: minor tweak
AlexAlexandre Nov 3, 2021
64675d7
feat: minor tweak
AlexAlexandre Nov 4, 2021
febb261
test: setup mocks for rn-fetch-blob and react-native-file-viewer
AlexAlexandre Nov 4, 2021
182ebab
Merge branch 'develop' into feat/download-file
AlexAlexandre Nov 4, 2021
4df0524
Merge branch 'develop' into feat/download-file
AlexAlexandre Nov 11, 2021
b6e7a38
refactor: changing the loading for show over the content
AlexAlexandre Nov 11, 2021
da32e38
refactor: changing the way to get the file extensions
AlexAlexandre Nov 11, 2021
dd6fdbf
refactor: downloading the file when unable to view
AlexAlexandre Nov 11, 2021
0e105c6
refactor: implementing a try catch
AlexAlexandre Nov 12, 2021
554df95
Minor style change on attachment loading
diegolmello Nov 12, 2021
d707d39
Disable button while loading
diegolmello Nov 12, 2021
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
16 changes: 16 additions & 0 deletions __tests__/Storyshots.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import initStoryshots from '@storybook/addon-storyshots';

jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: '/data/com.rocket.chat/documents',
DownloadDir: '/data/com.rocket.chat/downloads'
},
exists: jest.fn(() => null)
},
fetch: jest.fn(() => null),
config: jest.fn(() => null)
}));

jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null)
}));

jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());

Expand Down
4 changes: 4 additions & 0 deletions app/constants/localPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import RNFetchBlob from 'rn-fetch-blob';

export const DOCUMENTS_PATH = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
export const DOWNLOAD_PATH = `${RNFetchBlob.fs.dirs.DownloadDir}/`;
36 changes: 24 additions & 12 deletions app/containers/message/Reply.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import moment from 'moment';
import { transparentize } from 'color2k';
Expand All @@ -11,6 +11,9 @@ import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { fileDownloadAndPreview } from '../../utils/fileDownload';
import { formatAttachmentUrl } from '../../lib/utils';
import RCActivityIndicator from '../ActivityIndicator';

const styles = StyleSheet.create({
button: {
Expand Down Expand Up @@ -84,7 +87,7 @@ const styles = StyleSheet.create({
}
});

interface IMessageReplyAttachment {
export interface IMessageReplyAttachment {
Comment thread
AlexAlexandre marked this conversation as resolved.
Outdated
author_name: string;
message_link: string;
ts: string;
Expand Down Expand Up @@ -120,7 +123,7 @@ interface IMessageFields {
}

interface IMessageReply {
attachment: Partial<IMessageReplyAttachment>;
attachment: IMessageReplyAttachment;
timeFormat: string;
index: number;
theme: string;
Expand Down Expand Up @@ -209,12 +212,14 @@ const Fields = React.memo(

const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
const [loading, setLoading] = useState(false);

if (!attachment) {
return null;
}
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);

const onPress = () => {
const onPress = async () => {
let url = attachment.title_link || attachment.author_link;
if (attachment.message_link) {
return jumpToMessage(attachment.message_link);
Expand All @@ -223,10 +228,11 @@ const Reply = React.memo(
return;
}
if (attachment.type === 'file') {
if (!url.startsWith('http')) {
url = `${baseUrl}${url}`;
}
url = `${url}?rc_uid=${user.id}&rc_token=${user.token}`;
setLoading(true);
url = formatAttachmentUrl(attachment.title_link, user.id, user.token, baseUrl);
await fileDownloadAndPreview(url, attachment);
setLoading(false);
return;
}
openLink(url, theme);
};
Expand Down Expand Up @@ -256,10 +262,16 @@ const Reply = React.memo(
]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
<View style={styles.attachmentContainer}>
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
<UrlImage image={attachment.thumb_url} />
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
{loading ? (
Comment thread
AlexAlexandre marked this conversation as resolved.
<RCActivityIndicator theme={theme} />
) : (
<>
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
<UrlImage image={attachment.thumb_url} />
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
</>
)}
</View>
</Touchable>
{/* @ts-ignore*/}
Expand Down
42 changes: 36 additions & 6 deletions app/containers/message/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { StyleSheet } from 'react-native';
import { dequal } from 'dequal';

import Touchable from './Touchable';
import Markdown from '../markdown';
import openLink from '../../utils/openLink';
import { isIOS } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons';
import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { fileDownload } from '../../utils/fileDownload';
import EventEmitter from '../../utils/events';
import { LISTENER } from '../Toast';
import I18n from '../../i18n';
import RCActivityIndicator from '../ActivityIndicator';

const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
Expand All @@ -27,6 +31,9 @@ const styles = StyleSheet.create({

interface IMessageVideo {
file: {
title: string;
title_link: string;
type: string;
video_type: string;
video_url: string;
description: string;
Expand All @@ -39,15 +46,34 @@ interface IMessageVideo {
const Video = React.memo(
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => {
const { baseUrl, user } = useContext(MessageContext);
const [loading, setLoading] = useState(false);

if (!baseUrl) {
return null;
}
const onPress = () => {
const onPress = async () => {
if (isTypeSupported(file.video_type)) {
return showAttachment(file);
}
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
openLink(uri, theme);

if (!isIOS) {
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
await downloadVideo(uri);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my Android, it's just showing Saved to gallery.
I wonder if we should redirect the user to the default app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I didn't want to redirect the user, because is not all android has a default app to reproduce .WEB videos, so as we cant insurence that, I preferred just download the file to downloads folder.

return;
}
EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') });
};

const downloadVideo = async (uri: string) => {
setLoading(true);
const fileDownloaded = await fileDownload(uri, file);
setLoading(false);

if (fileDownloaded) {
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
return;
}
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') });
};

return (
Expand All @@ -56,7 +82,11 @@ const Video = React.memo(
onPress={onPress}
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
<CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />
{loading ? (
<RCActivityIndicator theme={theme} />
) : (
<CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />
)}
</Touchable>
{/* @ts-ignore*/}
<Markdown
Expand Down
6 changes: 4 additions & 2 deletions app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -782,5 +782,7 @@
"No_canned_responses": "No canned responses",
"Send_email_confirmation": "Send email confirmation",
"sending_email_confirmation": "sending email confirmation",
"Enable_Message_Parser": "Enable Message Parser"
}
"Enable_Message_Parser": "Enable Message Parser",
"Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file"
}
6 changes: 4 additions & 2 deletions app/i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,5 +733,7 @@
"Sharing": "Compartilhando",
"No_canned_responses": "Não há respostas predefinidas",
"Send_email_confirmation": "Enviar email de confirmação",
"sending_email_confirmation": "enviando email de confirmação"
}
"sending_email_confirmation": "enviando email de confirmação",
"Unsupported_format": "Formato não suportado",
"Downloaded_file": "Arquivo baixado"
}
59 changes: 59 additions & 0 deletions app/utils/fileDownload/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import RNFetchBlob, { FetchBlobResponse } from 'rn-fetch-blob';
import FileViewer from 'react-native-file-viewer';

import EventEmitter from '../events';
import { LISTENER } from '../../containers/Toast';
import I18n from '../../i18n';
import { DOCUMENTS_PATH, DOWNLOAD_PATH } from '../../constants/localPath';

interface IAttachment {
title: string;
title_link: string;
type: string;
description: string;
}

export const getExtensionType = (text: string): string | undefined => text.split('.').pop();

export const getLocalFilePathFromFile = (localPath: string, attachment: IAttachment): string => {
const fileName = attachment.title.split('.')[0];
return `${localPath}${fileName}.${getExtensionType(attachment.title_link)}`;
Comment thread
diegolmello marked this conversation as resolved.
Outdated
};

export const fileDownload = (url: string, attachment: IAttachment): Promise<FetchBlobResponse> => {
const path = getLocalFilePathFromFile(DOWNLOAD_PATH, attachment);

const options = {
path,
timeout: 10000,
indicator: true,
overwrite: true,
addAndroidDownloads: {
path,
notification: true,
useDownloadManager: true
}
};

return RNFetchBlob.config(options).fetch('GET', url);
};

export const fileDownloadAndPreview = async (url: string, attachment: IAttachment): Promise<void> => {
const path = getLocalFilePathFromFile(DOCUMENTS_PATH, attachment);
const file = await RNFetchBlob.config({
timeout: 10000,
indicator: true,
path
}).fetch('GET', url);

if (file) {
Comment thread
AlexAlexandre marked this conversation as resolved.
Outdated
FileViewer.open(file.data, {
showOpenWithDialog: true,
showAppsSuggestions: true
})
.then(res => res)
.catch(() => {
EventEmitter.emit(LISTENER, { message: I18n.t('Downloaded_file') });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my Android phone, it's only showing this message.
Is that because I don't have a default app?
I couldn't find where the file was saved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, on my Android tests, when the user don't have a default app installed we redirect to the play store, I forgot to upload the Android prints screens, but I did it right now.

I fixed the catch to download the files when the app can't show the preview, it's works now!

});
}
};
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ PODS:
- Firebase/Crashlytics (~> 6.27.0)
- React
- RNFBApp
- RNFileViewer (2.1.4):
- React-Core
- RNGestureHandler (1.10.3):
- React-Core
- RNImageCropPicker (0.36.3):
Expand Down Expand Up @@ -700,6 +702,7 @@ DEPENDENCIES:
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNLocalize (from `../node_modules/react-native-localize`)
Expand Down Expand Up @@ -894,6 +897,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/app"
RNFBCrashlytics:
:path: "../node_modules/@react-native-firebase/crashlytics"
RNFileViewer:
:path: "../node_modules/react-native-file-viewer"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNImageCropPicker:
Expand Down Expand Up @@ -1028,6 +1033,7 @@ SPEC CHECKSUMS:
RNFBAnalytics: dae6d7b280ba61c96e1bbdd34aca3154388f025e
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"react-native-document-picker": "5.2.0",
"react-native-easy-grid": "^0.2.2",
"react-native-easy-toast": "^1.2.0",
"react-native-file-viewer": "^2.1.4",
"react-native-gesture-handler": "^1.10.3",
"react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker",
"react-native-image-progress": "^1.1.1",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14254,6 +14254,11 @@ react-native-easy-toast@^1.2.0:
dependencies:
prop-types "^15.5.10"

react-native-file-viewer@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/react-native-file-viewer/-/react-native-file-viewer-2.1.4.tgz#987b2902f0f0ac87b42f3ac3d3037c8ae98f17a6"
integrity sha512-G3ko9lmqxT+lWhsDNy2K3Jes6xSMsUvlYwuwnRCNk2wC6hgYMeoeaiwDt8R3CdON781hB6Ej1eu3ir1QATtHXg==

react-native-flipper@^0.34.0:
version "0.34.0"
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"
Expand Down