mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
ADD: Save File Button
This commit is contained in:
parent
e22bf7d7d0
commit
945a255c7b
@ -28,7 +28,7 @@ const _shareOpen = async (filePath: string) => {
|
||||
* 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
|
||||
*/
|
||||
export const writeFileAndExport = async function (filename: string, contents: string) {
|
||||
export const writeFileAndExport = async function (filename: string, contents: string, showShareDialog: boolean = true) {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
@ -44,15 +44,18 @@ export const writeFileAndExport = async function (filename: string, contents: st
|
||||
|
||||
// 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}`;
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 30) {
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${filename}`;
|
||||
try {
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
console.log(`file saved to ${filePath}`);
|
||||
await _shareOpen(filePath);
|
||||
if (showShareDialog) {
|
||||
await _shareOpen(filePath);
|
||||
} else {
|
||||
presentAlert({ message: loc.formatString(loc.send.file_saved_at_path, { filePath }) });
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
presentAlert({ message: e.message });
|
||||
}
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
|
@ -40,19 +40,19 @@ export const Button: React.FC<ButtonProps> = props => {
|
||||
color: fontColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID={props.testID}
|
||||
style={[buttonStyle, props.style]}
|
||||
accessibilityRole="button"
|
||||
onPress={props.onPress}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<View style={styles.content}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={textStyle}>{props.title}</Text>}
|
||||
</View>
|
||||
const buttonView = (
|
||||
<View style={[buttonStyle, props.style, styles.content]}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={textStyle}>{props.title}</Text>}
|
||||
</View>
|
||||
);
|
||||
|
||||
return props.onPress ? (
|
||||
<TouchableOpacity testID={props.testID} accessibilityRole="button" onPress={props.onPress} disabled={props.disabled}>
|
||||
{buttonView}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
buttonView
|
||||
);
|
||||
};
|
||||
|
||||
|
41
components/SaveFileButton.tsx
Normal file
41
components/SaveFileButton.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Platform, StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import loc from '../loc';
|
||||
const fs = require('../blue_modules/fs');
|
||||
|
||||
interface SaveFileButtonProps {
|
||||
fileName: string;
|
||||
fileContent: string;
|
||||
children: ReactNode;
|
||||
onPress: () => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const SaveFileButton: React.FC<SaveFileButtonProps> = ({ fileName, fileContent, children, onPress, style }) => {
|
||||
const actions = [
|
||||
{ id: 'save', text: loc._.save_to_downloads },
|
||||
{ id: 'share', text: loc.receive.details_share },
|
||||
];
|
||||
|
||||
const handlePressMenuItem = (actionId: string) => {
|
||||
const action = actions.find(a => a.id === actionId);
|
||||
if (action?.id === 'save') {
|
||||
fs.writeFileAndExport(fileName, fileContent, false);
|
||||
} else if (action?.id === 'share') {
|
||||
fs.writeFileAndExport(fileName, fileContent, true);
|
||||
}
|
||||
};
|
||||
|
||||
return Platform.OS === 'android' ? (
|
||||
<ToolTipMenu isMenuPrimaryAction actions={actions} onPressMenuItem={handlePressMenuItem} buttonStyle={style}>
|
||||
{children}
|
||||
</ToolTipMenu>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onPress} style={style}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveFileButton;
|
@ -14,6 +14,7 @@ type SecondButtonProps = {
|
||||
disabled?: boolean;
|
||||
icon?: IconProps;
|
||||
title?: string;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
export const SecondButton = forwardRef<TouchableOpacity, SecondButtonProps>((props, ref) => {
|
||||
@ -25,13 +26,19 @@ export const SecondButton = forwardRef<TouchableOpacity, SecondButtonProps>((pro
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
}
|
||||
|
||||
return (
|
||||
const buttonView = (
|
||||
<View style={styles.view}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={[styles.text, { color: fontColor }]}>{props.title}</Text>}
|
||||
</View>
|
||||
);
|
||||
|
||||
return props.onPress ? (
|
||||
<TouchableOpacity accessibilityRole="button" style={[styles.button, { backgroundColor }]} {...props} ref={ref}>
|
||||
<View style={styles.view}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={[styles.text, { color: fontColor }]}>{props.title}</Text>}
|
||||
</View>
|
||||
{buttonView}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={[styles.button, { backgroundColor }]}>{buttonView}</View>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { useTheme } from './themes';
|
||||
|
||||
interface SquareButtonProps {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
onPress?: () => void;
|
||||
style: StyleProp<ViewStyle>;
|
||||
testID?: string;
|
||||
}
|
||||
@ -19,12 +19,18 @@ export const SquareButton = forwardRef<TouchableOpacity, SquareButtonProps>((pro
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
const buttonView = (
|
||||
<View style={styles.contentContainer}>
|
||||
<Text style={[styles.text, hookStyles.text]}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return onPress ? (
|
||||
<TouchableOpacity ref={ref} style={style} onPress={onPress} testID={testID} accessibilityRole="button">
|
||||
<View style={styles.contentContainer}>
|
||||
<Text style={[styles.text, hookStyles.text]}>{title}</Text>
|
||||
</View>
|
||||
{buttonView}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={style}>{buttonView}</View>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"save": "Save",
|
||||
"save_to_downloads": "Save to Downloads",
|
||||
"seed": "Seed",
|
||||
"success": "Success",
|
||||
"wallet_key": "Wallet key",
|
||||
@ -210,6 +211,7 @@
|
||||
"reset_amount_confirm": "Would you like to reset the amount?",
|
||||
"success_done": "Done",
|
||||
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder.",
|
||||
"file_saved_at_path": "The file has been saved in your Downloads folder ({filePath}).",
|
||||
"problem_with_psbt": "Problem with PSBT"
|
||||
},
|
||||
"settings": {
|
||||
|
@ -20,6 +20,7 @@ import ecc from '../blue_modules/noble_ecc';
|
||||
import Button from '../components/Button';
|
||||
import SafeArea from '../components/SafeArea';
|
||||
import presentAlert from '../components/Alert';
|
||||
import SaveFileButton from '../components/SaveFileButton';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const BlueCrypto = require('react-native-blue-crypto');
|
||||
const encryption = require('../blue_modules/encryption');
|
||||
@ -320,7 +321,12 @@ export default class Selftest extends Component {
|
||||
}
|
||||
})()}
|
||||
<BlueSpacing20 />
|
||||
<Button title="Test Save to Storage" onPress={this.onPressSaveToStorage} />
|
||||
<SaveFileButton
|
||||
fileName="bluewallet-selftest.txt"
|
||||
fileContent={'Success on ' + new Date().toUTCString()}
|
||||
onPress={this.onPressSaveToStorage}
|
||||
/>
|
||||
<Button title="Test Save to Storage" />
|
||||
<BlueSpacing20 />
|
||||
<Button title="Test File Import" onPress={this.onPressImportDocument} />
|
||||
</ScrollView>
|
||||
|
@ -13,6 +13,7 @@ import { requestCameraAuthorization } from '../../helpers/scan-qr';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { isDesktop } from '../../blue_modules/environment';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const fs = require('../../blue_modules/fs');
|
||||
|
||||
@ -111,7 +112,14 @@ const PsbtMultisigQRCode = () => {
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
|
||||
<SaveFileButton
|
||||
fileName={fileName}
|
||||
fileContent={psbt.toBase64()}
|
||||
onPress={exportPSBT}
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
>
|
||||
<SquareButton title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@ -18,6 +18,7 @@ import { useTheme } from '../../components/themes';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import { SecondButton } from '../../components/SecondButton';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const fs = require('../../blue_modules/fs');
|
||||
@ -264,15 +265,21 @@ const PsbtWithHardwareWallet = () => {
|
||||
title={loc.send.psbt_tx_open}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'share-alternative',
|
||||
type: 'entypo',
|
||||
color: colors.buttonTextColor,
|
||||
}}
|
||||
<SaveFileButton
|
||||
fileName={`${Date.now()}.psbt`}
|
||||
fileContent={typeof psbt === 'string' ? psbt : psbt.toBase64()}
|
||||
style={styles.exportButton}
|
||||
onPress={exportPSBT}
|
||||
title={loc.send.psbt_tx_export}
|
||||
/>
|
||||
>
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'share-alternative',
|
||||
type: 'entypo',
|
||||
color: colors.buttonTextColor,
|
||||
}}
|
||||
title={loc.send.psbt_tx_export}
|
||||
/>
|
||||
</SaveFileButton>
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.copyToClipboard}>
|
||||
<BlueCopyToClipboardButton
|
||||
|
@ -38,6 +38,7 @@ import Button from '../../components/Button';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import { isDesktop } from '../../blue_modules/environment';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
|
||||
const prompt = require('../../helpers/prompt');
|
||||
const A = require('../../blue_modules/analytics');
|
||||
@ -672,7 +673,14 @@ const WalletsAddMultisigStep2 = () => {
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportCosigner} title={loc.multisig.share} />
|
||||
<SaveFileButton
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
fileName={cosignerXpubFilename}
|
||||
fileContent={cosignerXpub}
|
||||
onPress={exportCosigner}
|
||||
>
|
||||
<SquareButton title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
@ -48,6 +48,7 @@ import ListItem from '../../components/ListItem';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Button from '../../components/Button';
|
||||
import { SecondButton } from '../../components/SecondButton';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
|
||||
const prompt = require('../../helpers/prompt');
|
||||
|
||||
@ -419,7 +420,7 @@ const WalletDetails = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onExportHistoryPressed = async () => {
|
||||
const exportHistoryContent = () => {
|
||||
const csvFileArray = [
|
||||
loc.transactions.date,
|
||||
loc.transactions.txid,
|
||||
@ -458,8 +459,11 @@ const WalletDetails = () => {
|
||||
|
||||
csvFile += '\n' + data.join(','); // CSV line
|
||||
}
|
||||
return csvFile;
|
||||
};
|
||||
|
||||
await writeFileAndExport(`${wallet.label.replace(' ', '-')}-history.csv`, csvFile);
|
||||
const onExportHistoryPressed = async () => {
|
||||
await writeFileAndExport(`${wallet.label.replace(' ', '-')}-history.csv`, exportHistoryContent);
|
||||
};
|
||||
|
||||
const handleDeleteButtonTapped = () => {
|
||||
@ -659,7 +663,13 @@ const WalletDetails = () => {
|
||||
{walletTransactionsLength > 0 && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={onExportHistoryPressed} title={loc.wallets.details_export_history} />
|
||||
<SaveFileButton
|
||||
fileName={`${wallet.getLabel().replace(' ', '-')}-history.csv`}
|
||||
fileContent={exportHistoryContent}
|
||||
onPress={onExportHistoryPressed}
|
||||
>
|
||||
<SecondButton title={loc.wallets.details_export_history} />
|
||||
</SaveFileButton>
|
||||
</>
|
||||
)}
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
|
@ -11,6 +11,7 @@ import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
import usePrivacy from '../../hooks/usePrivacy';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
const fs = require('../../blue_modules/fs');
|
||||
|
||||
const ExportMultisigCoordinationSetup = () => {
|
||||
@ -88,7 +89,14 @@ const ExportMultisigCoordinationSetup = () => {
|
||||
{isShareButtonTapped ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportTxtFile} title={loc.multisig.share} />
|
||||
<SaveFileButton
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
fileName={wallet.getLabel() + '.txt'}
|
||||
fileContent={wallet.getXpub()}
|
||||
onPress={exportTxtFile}
|
||||
>
|
||||
<SquareButton title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<BlueText style={[styles.secret, stylesHook.secret]}>{wallet.getXpub()}</BlueText>
|
||||
|
@ -51,6 +51,7 @@ import usePrivacy from '../../hooks/usePrivacy';
|
||||
import loc from '../../loc';
|
||||
import { isDesktop } from '../../blue_modules/environment';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
const fs = require('../../blue_modules/fs');
|
||||
const prompt = require('../../helpers/prompt');
|
||||
|
||||
@ -570,7 +571,6 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
|
||||
const isPad: boolean = Platform.isPad;
|
||||
|
||||
return (
|
||||
// @ts-ignore wtf doneButton
|
||||
<BottomModal isVisible={isShareModalVisible} onClose={hideShareModal} doneButton coverScreen={false}>
|
||||
<KeyboardAvoidingView enabled={!isPad} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
|
||||
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
|
||||
@ -580,7 +580,9 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
|
||||
<QRCodeComponent value={exportStringURv2} size={260} isLogoRendered={false} />
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.squareButtonWrapper}>
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportCosigner} title={loc.multisig.share} />
|
||||
<SaveFileButton fileContent={exportString} fileName={exportFilename} onPress={exportCosigner}>
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} title={loc.multisig.share} />
|
||||
</SaveFileButton>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
|
Loading…
Reference in New Issue
Block a user