mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-23 15:20:55 +01:00
Merge pull request #6014 from BlueWallet/fix-fs-save-and-open-android
Fix fs save and open android
This commit is contained in:
commit
53f29e3979
4 changed files with 85 additions and 100 deletions
|
@ -68,7 +68,7 @@
|
|||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleInstance"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
|
|
|
@ -3,75 +3,79 @@ import RNFS from 'react-native-fs';
|
|||
import Share from 'react-native-share';
|
||||
import loc from '../loc';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
|
||||
import { presentCameraNotAuthorizedAlert } from '../class/camera';
|
||||
import { isDesktop } from '../blue_modules/environment';
|
||||
import { launchImageLibrary } from 'react-native-image-picker';
|
||||
import { isDesktop } from './environment';
|
||||
import alert from '../components/Alert';
|
||||
import { readFile } from './react-native-bw-file-access';
|
||||
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
|
||||
const writeFileAndExportToAndroidDestionation = async ({ filename, contents, destinationLocalizedString, destination }) => {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) {
|
||||
const filePath = destination + `/${filename}`;
|
||||
try {
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
alert(loc.formatString(loc._.file_saved, { filePath: filename, destination: destinationLocalizedString }));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
alert(e.message);
|
||||
}
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [
|
||||
{
|
||||
text: loc.send.open_settings,
|
||||
onPress: () => {
|
||||
Linking.openSettings();
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
|
||||
]);
|
||||
}
|
||||
const _shareOpen = async (filePath: string) => {
|
||||
return await Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error.message);
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
const writeFileAndExport = async function (filename, contents) {
|
||||
/**
|
||||
* Writes a file to fs, and triggers an OS sharing dialog, so user can decide where to put this file (share to cloud
|
||||
* or perhabs messaging app). Provided filename should be just a file name, NOT a path
|
||||
*/
|
||||
const writeFileAndExport = async function (filename: string, contents: string) {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
await Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
await _shareOpen(filePath);
|
||||
} else if (Platform.OS === 'android') {
|
||||
await writeFileAndExportToAndroidDestionation({
|
||||
filename,
|
||||
contents,
|
||||
destinationLocalizedString: loc._.downloads_folder,
|
||||
destination: RNFS.DownloadDirectoryPath,
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
|
||||
// In Android 13 no WRITE_EXTERNAL_STORAGE permission is needed
|
||||
// @see https://stackoverflow.com/questions/76311685/permissionandroid-request-always-returns-never-ask-again-without-any-prompt-r
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) {
|
||||
const filePath = RNFS.DocumentDirectoryPath + `/${filename}`;
|
||||
try {
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
console.log(`file saved to ${filePath}`);
|
||||
await _shareOpen(filePath);
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
alert(e.message);
|
||||
}
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [
|
||||
{
|
||||
text: loc.send.open_settings,
|
||||
onPress: () => {
|
||||
Linking.openSettings();
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
alert('Not implemented for this platform');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw).
|
||||
*
|
||||
* @returns {Promise<string|boolean>} Base64 PSBT
|
||||
*/
|
||||
const openSignedTransaction = async function () {
|
||||
const openSignedTransaction = async function (): Promise<string | boolean> {
|
||||
try {
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
|
@ -87,7 +91,7 @@ const openSignedTransaction = async function () {
|
|||
return false;
|
||||
};
|
||||
|
||||
const _readPsbtFileIntoBase64 = async function (uri) {
|
||||
const _readPsbtFileIntoBase64 = async function (uri: string): Promise<string> {
|
||||
const base64 = await RNFS.readFile(uri, 'base64');
|
||||
const stringData = Buffer.from(base64, 'base64').toString(); // decode from base64
|
||||
if (stringData.startsWith('psbt')) {
|
||||
|
@ -106,19 +110,17 @@ const showImagePickerAndReadImage = () => {
|
|||
return new Promise((resolve, reject) =>
|
||||
launchImageLibrary(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
maxHeight: 800,
|
||||
maxWidth: 600,
|
||||
selectionLimit: 1,
|
||||
},
|
||||
response => {
|
||||
if (!response.didCancel) {
|
||||
const asset = response.assets[0];
|
||||
const asset = response.assets?.[0] ?? {};
|
||||
if (asset.uri) {
|
||||
const uri = asset.uri.toString().replace('file://', '');
|
||||
LocalQRCode.decode(uri, (error, result) => {
|
||||
LocalQRCode.decode(uri, (error: any, result: string) => {
|
||||
if (!error) {
|
||||
resolve(result);
|
||||
} else {
|
||||
|
@ -132,33 +134,7 @@ const showImagePickerAndReadImage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const takePhotoWithImagePickerAndReadPhoto = () => {
|
||||
return new Promise((resolve, reject) =>
|
||||
launchCamera(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
},
|
||||
response => {
|
||||
if (response.uri) {
|
||||
const uri = response.uri.toString().replace('file://', '');
|
||||
LocalQRCode.decode(uri, (error, result) => {
|
||||
if (!error) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new Error(loc.send.qr_error_no_qrcode));
|
||||
}
|
||||
});
|
||||
} else if (response.error) {
|
||||
presentCameraNotAuthorizedAlert(response.error);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const showFilePickerAndReadFile = async function () {
|
||||
const showFilePickerAndReadFile = async function (): Promise<{ data: string | false; uri: string | false }> {
|
||||
try {
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
copyTo: 'cachesDirectory',
|
||||
|
@ -175,21 +151,32 @@ const showFilePickerAndReadFile = async function () {
|
|||
: [DocumentPicker.types.allFiles],
|
||||
});
|
||||
|
||||
if (!res.fileCopyUri) {
|
||||
alert('Picking and caching a file failed');
|
||||
return { data: false, uri: false };
|
||||
}
|
||||
|
||||
const fileCopyUri = decodeURI(res.fileCopyUri);
|
||||
|
||||
let file = false;
|
||||
let file;
|
||||
if (res.fileCopyUri.toLowerCase().endsWith('.psbt')) {
|
||||
// this is either binary file from ElectrumDesktop OR string file with base64 string in there
|
||||
file = await _readPsbtFileIntoBase64(fileCopyUri);
|
||||
return { data: file, uri: decodeURI(res.fileCopyUri) };
|
||||
}
|
||||
|
||||
if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) {
|
||||
if (res.type === DocumentPicker.types.images || res.type?.startsWith('image/')) {
|
||||
return new Promise(resolve => {
|
||||
const uri2 = res.fileCopyUri.toString().replace('file://', '');
|
||||
LocalQRCode.decode(decodeURI(uri2), (error, result) => {
|
||||
if (!res.fileCopyUri) {
|
||||
// to make ts happy, should not need this check here
|
||||
alert('Picking and caching a file failed');
|
||||
resolve({ data: false, uri: false });
|
||||
return;
|
||||
}
|
||||
const uri2 = res.fileCopyUri.replace('file://', '');
|
||||
LocalQRCode.decode(decodeURI(uri2), (error: any, result: string) => {
|
||||
if (!error) {
|
||||
resolve({ data: result, fileCopyUri });
|
||||
resolve({ data: result, uri: fileCopyUri });
|
||||
} else {
|
||||
resolve({ data: false, uri: false });
|
||||
}
|
||||
|
@ -198,8 +185,8 @@ const showFilePickerAndReadFile = async function () {
|
|||
}
|
||||
|
||||
file = await RNFS.readFile(fileCopyUri);
|
||||
return { data: file, fileCopyUri };
|
||||
} catch (err) {
|
||||
return { data: file, uri: fileCopyUri };
|
||||
} catch (err: any) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert(err.message);
|
||||
}
|
||||
|
@ -207,12 +194,13 @@ const showFilePickerAndReadFile = async function () {
|
|||
}
|
||||
};
|
||||
|
||||
// todo expand with other platforms if necessary
|
||||
const readFileOutsideSandbox = filePath => {
|
||||
const readFileOutsideSandbox = (filePath: string) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return readFile(filePath);
|
||||
} else {
|
||||
} else if (Platform.OS === 'android') {
|
||||
return RNFS.readFile(filePath);
|
||||
} else {
|
||||
alert('Not implemented for this platform');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -220,5 +208,4 @@ module.exports.writeFileAndExport = writeFileAndExport;
|
|||
module.exports.openSignedTransaction = openSignedTransaction;
|
||||
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
|
||||
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
|
||||
module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto;
|
||||
module.exports.readFileOutsideSandbox = readFileOutsideSandbox;
|
|
@ -16,8 +16,6 @@
|
|||
"success": "Success",
|
||||
"wallet_key": "Wallet key",
|
||||
"invalid_animated_qr_code_fragment": "Invalid animated QR Code fragment. Please try again.",
|
||||
"file_saved": "File {filePath} has been saved in your {destination}.",
|
||||
"downloads_folder": "Downloads Folder",
|
||||
"close": "Close",
|
||||
"change_input_currency": "Change input currency",
|
||||
"refresh": "Refresh",
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class Selftest extends Component {
|
|||
}
|
||||
|
||||
onPressSaveToStorage = () => {
|
||||
fs.writeFileAndExport('bluewallet-storagesave-test.txt', 'Success');
|
||||
fs.writeFileAndExport('bluewallet-storagesave-test.txt', 'Success on ' + new Date().toUTCString());
|
||||
};
|
||||
|
||||
onPressImportDocument = async () => {
|
||||
|
@ -318,7 +318,7 @@ export default class Selftest extends Component {
|
|||
<BlueSpacing20 />
|
||||
<Button title="Test Save to Storage" onPress={this.onPressSaveToStorage} />
|
||||
<BlueSpacing20 />
|
||||
<Button title="Test Save to File Import" onPress={this.onPressImportDocument} />
|
||||
<Button title="Test File Import" onPress={this.onPressImportDocument} />
|
||||
</ScrollView>
|
||||
</BlueCard>
|
||||
</SafeArea>
|
||||
|
|
Loading…
Add table
Reference in a new issue