mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-23 06:45:08 +01:00
ADD: Privacy Settings Screen (#1967)
* ADD: Privacy Settings Screen * REF: Clipboard read opt-out * ADD: Wallet shortcut privacy * FIX: Remove unused code * REF:Addasync/await * REF: BlueClipboard * REF: Rename file
This commit is contained in:
parent
04a85f8272
commit
5ece75b691
8 changed files with 222 additions and 30 deletions
10
App.js
10
App.js
|
@ -11,7 +11,6 @@ import {
|
|||
Platform,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Modal from 'react-native-modal';
|
||||
import { NavigationContainer, CommonActions } from '@react-navigation/native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
|
@ -27,8 +26,8 @@ import DeeplinkSchemaMatch from './class/deeplink-schema-match';
|
|||
import loc from './loc';
|
||||
import { BlueDefaultTheme, BlueDarkTheme, BlueCurrentTheme } from './components/themes';
|
||||
import InitRoot from './Navigation';
|
||||
import BlueClipboard from './blue_modules/clipboard';
|
||||
const A = require('./blue_modules/analytics');
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
Sentry.init({
|
||||
dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736',
|
||||
|
@ -188,10 +187,9 @@ export default class App extends React.Component {
|
|||
if (BlueApp.getWallets().length > 0) {
|
||||
if ((this.state.appState.match(/background/) && nextAppState) === 'active' || nextAppState === undefined) {
|
||||
setTimeout(() => A(A.ENUM.APP_UNSUSPENDED), 2000);
|
||||
|
||||
const processed = await this._processPushNotifications();
|
||||
if (processed) return;
|
||||
const clipboard = await Clipboard.getString();
|
||||
const clipboard = await BlueClipboard.getClipboardContent();
|
||||
const isAddressFromStoredWallet = BlueApp.getWallets().some(wallet => {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
// checking address validity is faster than unwrapping hierarchy only to compare it to garbage
|
||||
|
@ -278,10 +276,10 @@ export default class App extends React.Component {
|
|||
<View style={styles.space} />
|
||||
<BlueButton
|
||||
noMinWidth
|
||||
title="OK"
|
||||
title={loc._.ok}
|
||||
onPress={() => {
|
||||
this.setState({ isClipboardContentModalVisible: false }, async () => {
|
||||
const clipboard = await Clipboard.getString();
|
||||
const clipboard = await BlueClipboard.getClipboardContent();
|
||||
setTimeout(() => this.handleOpenURL({ url: clipboard }), 100);
|
||||
});
|
||||
}}
|
||||
|
|
|
@ -69,7 +69,7 @@ export const BlueButton = props => {
|
|||
const { colors } = useTheme();
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor || BlueCurrentTheme.colors.mainColor;
|
||||
let fontColor = colors.buttonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
|
@ -746,9 +746,15 @@ export const BlueListItem = React.memo(props => {
|
|||
</ListItem.Title>
|
||||
)}
|
||||
</ListItem.Content>
|
||||
{props.chevron && <ListItem.Chevron />}
|
||||
{props.rightIcon && <Avatar icon={props.rightIcon} />}
|
||||
{props.switch && <Switch {...props.switch} />}
|
||||
{props.isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<>
|
||||
{props.chevron && <ListItem.Chevron />}
|
||||
{props.rightIcon && <Avatar icon={props.rightIcon} />}
|
||||
{props.switch && <Switch {...props.switch} />}
|
||||
</>
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -70,6 +70,7 @@ import UnlockWith from './UnlockWith';
|
|||
import { BlueNavigationStyle } from './BlueComponents';
|
||||
import DrawerList from './screen/wallets/drawerList';
|
||||
import { isTablet } from 'react-native-device-info';
|
||||
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
|
||||
|
||||
const defaultScreenOptions =
|
||||
Platform.OS === 'ios'
|
||||
|
@ -136,6 +137,7 @@ const WalletsRoot = () => (
|
|||
<WalletsStack.Screen name="PlausibleDeniability" component={PlausibleDeniability} options={PlausibleDeniability.navigationOptions} />
|
||||
<WalletsStack.Screen name="LightningSettings" component={LightningSettings} options={LightningSettings.navigationOptions} />
|
||||
<WalletsStack.Screen name="ElectrumSettings" component={ElectrumSettings} options={ElectrumSettings.navigationOptions} />
|
||||
<WalletsStack.Screen name="SettingsPrivacy" component={SettingsPrivacy} options={SettingsPrivacy.navigationOptions} />
|
||||
<WalletsStack.Screen
|
||||
name="LNDViewInvoice"
|
||||
component={LNDViewInvoice}
|
||||
|
|
38
blue_modules/clipboard.js
Normal file
38
blue_modules/clipboard.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { useAsyncStorage } from '@react-native-community/async-storage';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
function BlueClipboard() {
|
||||
BlueClipboard.STORAGE_KEY = 'ClipboardReadAllowed';
|
||||
const isClipboardAccessAllowed = useAsyncStorage(BlueClipboard.STORAGE_KEY).getItem;
|
||||
const setIsClipboardAccessAllowed = useAsyncStorage(BlueClipboard.STORAGE_KEY).setItem;
|
||||
|
||||
BlueClipboard.isReadClipboardAllowed = async () => {
|
||||
try {
|
||||
const clipboardAccessAllowed = await isClipboardAccessAllowed();
|
||||
if (clipboardAccessAllowed === null) {
|
||||
await setIsClipboardAccessAllowed(JSON.stringify(true));
|
||||
return true;
|
||||
}
|
||||
return !!JSON.parse(clipboardAccessAllowed);
|
||||
} catch {
|
||||
await setIsClipboardAccessAllowed(JSON.stringify(true));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BlueClipboard.setReadClipboardAllowed = value => {
|
||||
setIsClipboardAccessAllowed(JSON.stringify(!!value));
|
||||
};
|
||||
|
||||
BlueClipboard.getClipboardContent = async () => {
|
||||
const isAllowed = await BlueClipboard.isReadClipboardAllowed();
|
||||
if (isAllowed) {
|
||||
return Clipboard.getString();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
BlueClipboard.default = new BlueClipboard();
|
||||
export default BlueClipboard;
|
|
@ -1,11 +1,38 @@
|
|||
import QuickActions from 'react-native-quick-actions';
|
||||
import { Platform } from 'react-native';
|
||||
import { formatBalance } from '../loc';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
|
||||
export default class DeviceQuickActions {
|
||||
static shared = new DeviceQuickActions();
|
||||
static STORAGE_KEY = 'DeviceQuickActionsEnabled';
|
||||
wallets;
|
||||
|
||||
static setEnabled(enabled = true) {
|
||||
return AsyncStorage.setItem(DeviceQuickActions.STORAGE_KEY, JSON.stringify(enabled)).then(() => {
|
||||
if (!enabled) {
|
||||
DeviceQuickActions.clearShortcutItems();
|
||||
} else {
|
||||
const BlueApp = require('../BlueApp');
|
||||
DeviceQuickActions.shared.wallets = BlueApp.getWallets();
|
||||
DeviceQuickActions.setQuickActions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async getEnabled() {
|
||||
try {
|
||||
const isEnabled = await AsyncStorage.getItem(DeviceQuickActions.STORAGE_KEY);
|
||||
if (isEnabled === null) {
|
||||
await DeviceQuickActions.setEnabled(JSON.stringify(true));
|
||||
return true;
|
||||
}
|
||||
return !!JSON.parse(isEnabled);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static setWallets(wallets) {
|
||||
DeviceQuickActions.shared.wallets = wallets.slice(0, 4);
|
||||
}
|
||||
|
@ -14,30 +41,35 @@ export default class DeviceQuickActions {
|
|||
DeviceQuickActions.shared.wallets = undefined;
|
||||
}
|
||||
|
||||
static setQuickActions() {
|
||||
static async setQuickActions() {
|
||||
if (DeviceQuickActions.shared.wallets === undefined) {
|
||||
return;
|
||||
}
|
||||
QuickActions.isSupported((error, _supported) => {
|
||||
if (error === null) {
|
||||
const shortcutItems = [];
|
||||
for (const wallet of DeviceQuickActions.shared.wallets) {
|
||||
shortcutItems.push({
|
||||
type: 'Wallets', // Required
|
||||
title: wallet.getLabel(), // Optional, if empty, `type` will be used instead
|
||||
subtitle:
|
||||
wallet.hideBalance || wallet.getBalance() <= 0
|
||||
? ''
|
||||
: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
|
||||
userInfo: {
|
||||
url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL
|
||||
},
|
||||
icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }),
|
||||
});
|
||||
|
||||
if (await DeviceQuickActions.getEnabled()) {
|
||||
QuickActions.isSupported((error, _supported) => {
|
||||
if (error === null) {
|
||||
const shortcutItems = [];
|
||||
for (const wallet of DeviceQuickActions.shared.wallets) {
|
||||
shortcutItems.push({
|
||||
type: 'Wallets', // Required
|
||||
title: wallet.getLabel(), // Optional, if empty, `type` will be used instead
|
||||
subtitle:
|
||||
wallet.hideBalance || wallet.getBalance() <= 0
|
||||
? ''
|
||||
: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
|
||||
userInfo: {
|
||||
url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL
|
||||
},
|
||||
icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }),
|
||||
});
|
||||
}
|
||||
QuickActions.setShortcutItems(shortcutItems);
|
||||
}
|
||||
QuickActions.setShortcutItems(shortcutItems);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
DeviceQuickActions.clearShortcutItems();
|
||||
}
|
||||
}
|
||||
|
||||
static clearShortcutItems() {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"of": "{number} of {total}",
|
||||
"ok": "OK",
|
||||
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it",
|
||||
"allow": "Allow",
|
||||
"dont_allow": "Don't Allow",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"invalid_animated_qr_code_fragment" : "Invalid animated QRCode fragment, please try again",
|
||||
|
@ -273,6 +275,13 @@
|
|||
"password_explain": "Create the password you will use to decrypt the storage",
|
||||
"passwords_do_not_match": "Passwords do not match",
|
||||
"plausible_deniability": "Plausible deniability",
|
||||
"privacy": "Privacy",
|
||||
"privacy_read_clipboard": "Read Clipboard",
|
||||
"privacy_read_clipboard_alert": "BlueWallet will display shortcuts for handling an invoice or address found in your clipboard.",
|
||||
"privacy_system_settings": "System Settings",
|
||||
"privacy_quickactions": "Wallet Shortcuts",
|
||||
"privacy_quickactions_explanation": "Touch and hold the BlueWallet app icon on your Home Screen to quickly view your wallet's balance.",
|
||||
"privacy_clipboard_explanation": "Provide shortcuts if an address, or invoice, is found in your clipboard.",
|
||||
"push_notifications": "Push notifications",
|
||||
"retype_password": "Re-type password",
|
||||
"save": "Save",
|
||||
|
|
106
screen/settings/SettingsPrivacy.js
Normal file
106
screen/settings/SettingsPrivacy.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, TouchableWithoutFeedback, StyleSheet, Linking } from 'react-native';
|
||||
import { BlueTextHooks, BlueSpacing20, BlueListItem, BlueNavigationStyle, BlueCard } from '../../BlueComponents';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import loc from '../../loc';
|
||||
import BlueClipboard from '../../blue_modules/clipboard';
|
||||
import DeviceQuickActions from '../../class/quick-actions';
|
||||
const BlueApp = require('../../BlueApp');
|
||||
|
||||
const SettingsPrivacy = () => {
|
||||
const { colors } = useTheme();
|
||||
const sections = Object.freeze({ ALL: 0, CLIPBOARDREAD: 1, QUICKACTION: 2 });
|
||||
const [isLoading, setIsLoading] = useState(sections.ALL);
|
||||
const [isReadClipboardAllowed, setIsReadClipboardAllowed] = useState(false);
|
||||
const [isQuickActionsEnabled, setIsQuickActionsEnabled] = useState(false);
|
||||
const [storageIsEncrypted, setStorageIsEncrypted] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setIsReadClipboardAllowed(await BlueClipboard.isReadClipboardAllowed());
|
||||
setStorageIsEncrypted(await BlueApp.storageIsEncrypted());
|
||||
setIsQuickActionsEnabled(await DeviceQuickActions.getEnabled());
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const onValueChange = async value => {
|
||||
setIsLoading(sections.CLIPBOARDREAD);
|
||||
try {
|
||||
await BlueClipboard.setReadClipboardAllowed(value);
|
||||
setIsReadClipboardAllowed(value);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onQuickActionsValueChange = async value => {
|
||||
setIsLoading(sections.QUICKACTION);
|
||||
try {
|
||||
await DeviceQuickActions.setEnabled(value);
|
||||
setIsQuickActionsEnabled(value);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const stylesWithThemeHook = StyleSheet.create({
|
||||
root: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
});
|
||||
|
||||
const openApplicationSettings = () => {
|
||||
Linking.openSettings();
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView style={[styles.root, stylesWithThemeHook.root]}>
|
||||
<BlueListItem
|
||||
hideChevron
|
||||
title={loc.settings.privacy_read_clipboard}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ onValueChange, value: isReadClipboardAllowed, disabled: isLoading === sections.ALL }}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueTextHooks>{loc.settings.privacy_clipboard_explanation}</BlueTextHooks>
|
||||
</BlueCard>
|
||||
<BlueSpacing20 />
|
||||
{!storageIsEncrypted && (
|
||||
<>
|
||||
<BlueListItem
|
||||
hideChevron
|
||||
title={loc.settings.privacy_quickactions}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ onValueChange: onQuickActionsValueChange, value: isQuickActionsEnabled, disabled: isLoading === sections.ALL }}
|
||||
/>
|
||||
<BlueCard>
|
||||
<BlueTextHooks>{loc.settings.privacy_quickactions_explanation}</BlueTextHooks>
|
||||
</BlueCard>
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<BlueListItem title={loc.settings.privacy_system_settings} chevron onPress={openApplicationSettings} />
|
||||
<BlueSpacing20 />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
SettingsPrivacy.navigationOptions = () => ({
|
||||
...BlueNavigationStyle(),
|
||||
title: loc.settings.privacy,
|
||||
});
|
||||
|
||||
export default SettingsPrivacy;
|
|
@ -34,6 +34,7 @@ const Settings = () => {
|
|||
onPress={() => navigate('NotificationSettings')}
|
||||
chevron
|
||||
/>
|
||||
<BlueListItem title={loc.settings.privacy} component={TouchableOpacity} onPress={() => navigate('SettingsPrivacy')} chevron />
|
||||
<BlueListItem
|
||||
title={loc.settings.about}
|
||||
component={TouchableOpacity}
|
||||
|
|
Loading…
Add table
Reference in a new issue