Skip to content

Commit

Permalink
DMAPP-142: File Download Issue in Chat Module (#148)
Browse files Browse the repository at this point in the history
- Resolved file download handling for the chat module:
  - On Android devices, files will now be saved directly to the public Download directory.
  - On iOS devices, files will be temporarily cached, allowing users to manually save them permanently or share them as needed.
  • Loading branch information
meKushdeepSingh authored Jan 7, 2025
1 parent 67a6250 commit 3e01dd4
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 10 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<uses-feature android:name="android.hardware.audio.output"/>
<uses-feature android:name="android.hardware.microphone"/>
<uses-permission android:name="android.permission.POST_NOTIFICATION"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
Expand Down
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ PODS:
- React-jsinspector (0.71.14)
- React-logger (0.71.14):
- glog
- react-native-blob-util (0.19.11):
- React-Core
- react-native-config (1.5.0):
- react-native-config/App (= 1.5.0)
- react-native-config/App (1.5.0):
Expand Down Expand Up @@ -643,6 +645,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
- react-native-config (from `../node_modules/react-native-config`)
- react-native-encrypted-storage (from `../node_modules/react-native-encrypted-storage`)
- react-native-fast-openpgp (from `../node_modules/react-native-fast-openpgp`)
Expand Down Expand Up @@ -800,6 +803,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-blob-util:
:path: "../node_modules/react-native-blob-util"
react-native-config:
:path: "../node_modules/react-native-config"
react-native-encrypted-storage:
Expand Down Expand Up @@ -959,6 +964,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 94cfc1788637ceaf8841ef1f69b10cc0d62baadc
React-jsinspector: 7bf923954b4e035f494b01ac16633963412660d7
React-logger: 655ff5db8bd922acfbe76a4983ffab048916343e
react-native-blob-util: 39a20f2ef11556d958dc4beb0aa07d1ef2690745
react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727
react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7
react-native-fast-openpgp: f4158eb50244457e12f9d2aec06bf7cfcba314ef
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"node-libs-browser": "^2.2.1",
"react": "18.2.0",
"react-native": "0.71.14",
"react-native-blob-util": "^0.19.11",
"react-native-cache": "^2.0.3",
"react-native-callkeep": "4.3.8",
"react-native-config": "^1.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import {FontAwesome} from '@expo/vector-icons';
import {IMessageIPFS} from '@pushprotocol/restapi';
import React from 'react';
import {Linking, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {SvgUri} from 'react-native-svg';
import {useToaster} from 'src/contexts/ToasterContext';
import {formatAMPM} from 'src/helpers/DateTimeHelper';

import {useFileDownload} from '../../helpers/useFileDownload';

export interface FileMessageContent {
content: string;
name: string;
Expand Down Expand Up @@ -42,6 +46,13 @@ export const FileMessageComponent = ({
const content = fileContent.content as string;
const size = fileContent.size;

const {toastRef} = useToaster();
const [isDownloading, saveBase64File] = useFileDownload(
content,
name,
toastRef,
);

return (
<View
style={
Expand All @@ -62,16 +73,14 @@ export const FileMessageComponent = ({
{formatFileSize(size)}
</Text>
</View>
{!messageType && (
<TouchableOpacity
onPress={() =>
Linking.openURL(content).catch(e => {
console.log('err', e);
})
}>
<FontAwesome name="download" size={24} color="gray" />
</TouchableOpacity>
)}
{!messageType &&
(isDownloading ? (
<ActivityIndicator color="gray" size={'small'} />
) : (
<TouchableOpacity onPress={() => saveBase64File()}>
<FontAwesome name="download" size={24} color="gray" />
</TouchableOpacity>
))}

{!messageType && (
<Text style={styles.time}>{formatAMPM(chatMessage.timestamp!)}</Text>
Expand Down
107 changes: 107 additions & 0 deletions src/navigation/screens/chats/helpers/useFileDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {useState} from 'react';
import {PermissionsAndroid, Platform, Share} from 'react-native';
import RNBlobUtil from 'react-native-blob-util';
import {Toaster, ToasterOptions} from 'src/components/indicators/Toaster';

const androidDownloadDir = '/storage/emulated/0/Download';

const useFileDownload = (
base64DataWithPrefix: string,
fileName: string,
toastRef?: React.RefObject<Toaster>,
): [boolean, Function] => {
const [isDownloading, setIsDownloading] = useState(false);

// Request Storage Permissions (Android)
const requestStoragePermission = async () => {
if (Platform.OS === 'android' && Platform.Version <= 28) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Storage Permission Required',
message:
"The PUSH app requires access to your device's storage to download files.",
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);

return granted === PermissionsAndroid.RESULTS.GRANTED;
}
return true; // Permissions are automatically granted for iOS and Android 10+
};

const handleIOSDownload = (filePath: string) => {
// Share will give option to download the file on iOS Public directory
Share.share({
url: `file://${filePath}`,
})
.then(res => {
if (
res.action === 'sharedAction' &&
res.activityType === 'com.apple.DocumentManagerUICore.SaveToFiles'
) {
if (toastRef) {
toastRef.current?.showToast(
'File Saved successfully!',
'',
ToasterOptions.TYPE.GRADIENT_PRIMARY,
);
}
}
})
.catch(err => console.log('iOS share error', err))
.finally(() => setIsDownloading(false));
};

const handleAndroidDownload = () => {
// file is already saved in android public directory
setIsDownloading(false);
if (toastRef) {
toastRef.current?.showToast(
'File downloaded successfully!',
'',
ToasterOptions.TYPE.GRADIENT_PRIMARY,
);
}
};

// Save Base64 File
const saveBase64File = async () => {
setIsDownloading(true);
try {
// Request permission for Android
const hasPermission = await requestStoragePermission();
if (!hasPermission) {
console.log('Storage permission denied');
setIsDownloading(false);
return;
}

// Remove MIME type prefix from Base64 string
const base64Data = base64DataWithPrefix.split(',')[1];

// Define the file path
const filePath =
Platform.OS === 'android'
? `${androidDownloadDir}/${fileName}` // Public Downloads on Android
: `${RNBlobUtil.fs.dirs.CacheDir}/${fileName}`; // Documents directory on iOS

// Write the file
await RNBlobUtil.fs.writeFile(filePath, base64Data, 'base64');
if (Platform.OS === 'ios') {
handleIOSDownload(filePath);
} else {
handleAndroidDownload();
}
} catch (error) {
console.error('Error saving file:', error);
setIsDownloading(false);
}
};

return [isDownloading, saveBase64File];
};

export {useFileDownload};
21 changes: 21 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6929,6 +6929,13 @@ __metadata:
languageName: node
linkType: hard

"base-64@npm:0.1.0":
version: 0.1.0
resolution: "base-64@npm:0.1.0"
checksum: 10/5a42938f82372ab5392cbacc85a5a78115cbbd9dbef9f7540fa47d78763a3a8bd7d598475f0d92341f66285afd377509851a9bb5c67bbecb89686e9255d5b3eb
languageName: node
linkType: hard

"base-64@npm:^1.0.0":
version: 1.0.0
resolution: "base-64@npm:1.0.0"
Expand Down Expand Up @@ -16261,6 +16268,7 @@ __metadata:
prettier: "npm:^2.7.1"
react: "npm:18.2.0"
react-native: "npm:0.71.14"
react-native-blob-util: "npm:^0.19.11"
react-native-cache: "npm:^2.0.3"
react-native-callkeep: "npm:4.3.8"
react-native-config: "npm:^1.5.0"
Expand Down Expand Up @@ -16538,6 +16546,19 @@ __metadata:
languageName: node
linkType: hard

"react-native-blob-util@npm:^0.19.11":
version: 0.19.11
resolution: "react-native-blob-util@npm:0.19.11"
dependencies:
base-64: "npm:0.1.0"
glob: "npm:^10.3.10"
peerDependencies:
react: "*"
react-native: "*"
checksum: 10/b5496b2d41ea76fc4ff85d3f88ec1c6b2d7d409b88b1d71d5a51edf64564fa0f9f444d1cd634e641f043387ca04425a9e7e337670870a528095afd64223186f9
languageName: node
linkType: hard

"react-native-cache@npm:^2.0.3":
version: 2.0.3
resolution: "react-native-cache@npm:2.0.3"
Expand Down

0 comments on commit 3e01dd4

Please sign in to comment.