1
0
mirror of https://github.com/trezor/trezor-wallet synced 2025-03-12 22:56:05 +00:00

Merge pull request #454 from trezor/feature/stealth-mode

Feature/stealth mode (hidden balance)
This commit is contained in:
Vladimir Volek 2019-03-20 22:54:20 +01:00 committed by GitHub
commit 8e3258b8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 343 additions and 129 deletions

View File

@ -82,6 +82,7 @@
"styled-normalize": "^8.0.6", "styled-normalize": "^8.0.6",
"trezor-bridge-communicator": "1.0.2", "trezor-bridge-communicator": "1.0.2",
"trezor-connect": "7.0.0-beta.3", "trezor-connect": "7.0.0-beta.3",
"trezor-ui-components": "^1.0.0-beta.4",
"wallet-address-validator": "^0.2.4", "wallet-address-validator": "^0.2.4",
"web3": "1.0.0-beta.35", "web3": "1.0.0-beta.35",
"webpack": "^4.29.3", "webpack": "^4.29.3",

View File

@ -58,6 +58,7 @@ const KEY_PENDING: string = `${STORAGE_PATH}pending`;
const KEY_BETA_MODAL: string = '/betaModalPrivacy'; // this key needs to be compatible with "parent" (old) wallet const KEY_BETA_MODAL: string = '/betaModalPrivacy'; // this key needs to be compatible with "parent" (old) wallet
const KEY_LANGUAGE: string = `${STORAGE_PATH}language`; const KEY_LANGUAGE: string = `${STORAGE_PATH}language`;
const KEY_LOCAL_CURRENCY: string = `${STORAGE_PATH}localCurrency`; const KEY_LOCAL_CURRENCY: string = `${STORAGE_PATH}localCurrency`;
const KEY_HIDE_BALANCE: string = `${STORAGE_PATH}hideBalance`;
// https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js // https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js
// or // or
@ -282,6 +283,11 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
if (localCurrency) { if (localCurrency) {
dispatch(WalletActions.setLocalCurrency(JSON.parse(localCurrency))); dispatch(WalletActions.setLocalCurrency(JSON.parse(localCurrency)));
} }
const hideBalance: ?boolean = storageUtils.get(TYPE, KEY_HIDE_BALANCE);
if (hideBalance) {
dispatch(WalletActions.setHideBalance(hideBalance));
}
}; };
export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
@ -303,6 +309,11 @@ export const setLanguage = (): ThunkAction => (dispatch: Dispatch, getState: Get
storageUtils.set(TYPE, KEY_LANGUAGE, JSON.stringify(language)); storageUtils.set(TYPE, KEY_LANGUAGE, JSON.stringify(language));
}; };
export const setHideBalance = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const { hideBalance } = getState().wallet;
storageUtils.set(TYPE, KEY_HIDE_BALANCE, hideBalance);
};
export const setLocalCurrency = (): ThunkAction => ( export const setLocalCurrency = (): ThunkAction => (
dispatch: Dispatch, dispatch: Dispatch,
getState: GetState getState: GetState

View File

@ -62,6 +62,10 @@ export type WalletAction =
| { | {
type: typeof WALLET.SET_LOCAL_CURRENCY, type: typeof WALLET.SET_LOCAL_CURRENCY,
localCurrency: string, localCurrency: string,
}
| {
type: typeof WALLET.SET_HIDE_BALANCE,
toggled: boolean,
}; };
export const init = (): ThunkAction => (dispatch: Dispatch): void => { export const init = (): ThunkAction => (dispatch: Dispatch): void => {
@ -113,6 +117,11 @@ export const setLocalCurrency = (localCurrency: string): WalletAction => ({
localCurrency: localCurrency.toLowerCase(), localCurrency: localCurrency.toLowerCase(),
}); });
export const setHideBalance = (toggled: boolean): WalletAction => ({
type: WALLET.SET_HIDE_BALANCE,
toggled,
});
// This method will be called after each DEVICE.CONNECT action // This method will be called after each DEVICE.CONNECT action
// if connected device has different "passphrase_protection" settings than saved instances // if connected device has different "passphrase_protection" settings than saved instances
// all saved instances will be removed immediately inside DevicesReducer // all saved instances will be removed immediately inside DevicesReducer

View File

@ -18,3 +18,4 @@ export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_da
export const TOGGLE_SIDEBAR: 'wallet__toggle_sidebar' = 'wallet__toggle_sidebar'; export const TOGGLE_SIDEBAR: 'wallet__toggle_sidebar' = 'wallet__toggle_sidebar';
export const SET_LANGUAGE: 'wallet__set_language' = 'wallet__set_language'; export const SET_LANGUAGE: 'wallet__set_language' = 'wallet__set_language';
export const SET_LOCAL_CURRENCY: 'wallet__set_local_currency' = 'wallet__set_local_currency'; export const SET_LOCAL_CURRENCY: 'wallet__set_local_currency' = 'wallet__set_local_currency';
export const SET_HIDE_BALANCE: 'wallet__set_hide_balance' = 'wallet__set_hide_balance';

View File

@ -13,8 +13,9 @@ type State = {
ready: boolean, ready: boolean,
online: boolean, online: boolean,
language: string, language: string,
localCurrency: string,
messages: { [string]: string }, messages: { [string]: string },
localCurrency: string,
hideBalance: boolean,
dropdownOpened: boolean, dropdownOpened: boolean,
showBetaDisclaimer: boolean, showBetaDisclaimer: boolean,
showSidebar: ?boolean, showSidebar: ?boolean,
@ -29,8 +30,9 @@ const initialState: State = {
ready: false, ready: false,
online: navigator.onLine, online: navigator.onLine,
language: 'en', language: 'en',
localCurrency: 'usd',
messages: {}, messages: {},
localCurrency: 'usd',
hideBalance: false,
dropdownOpened: false, dropdownOpened: false,
firstLocationChange: true, firstLocationChange: true,
showBetaDisclaimer: false, showBetaDisclaimer: false,
@ -137,6 +139,12 @@ export default function wallet(state: State = initialState, action: Action): Sta
localCurrency: action.localCurrency, localCurrency: action.localCurrency,
}; };
case WALLET.SET_HIDE_BALANCE:
return {
...state,
hideBalance: action.toggled,
};
default: default:
return state; return state;
} }

View File

@ -26,6 +26,10 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar
api.dispatch(LocalStorageActions.setLanguage()); api.dispatch(LocalStorageActions.setLanguage());
break; break;
case WALLET.SET_HIDE_BALANCE:
api.dispatch(LocalStorageActions.setHideBalance());
break;
case WALLET.SET_LOCAL_CURRENCY: case WALLET.SET_LOCAL_CURRENCY:
api.dispatch(LocalStorageActions.setLocalCurrency()); api.dispatch(LocalStorageActions.setLocalCurrency());
break; break;

View File

@ -18,6 +18,7 @@ import FirmwareUnsupported from './components/FirmwareUnsupported';
import l10nMessages from './index.messages'; import l10nMessages from './index.messages';
type Props = { type Props = {
className?: string,
children?: React.Node, children?: React.Node,
isLoading?: boolean, isLoading?: boolean,
loader?: $ElementType<$ElementType<State, 'selectedAccount'>, 'loader'>, loader?: $ElementType<$ElementType<State, 'selectedAccount'>, 'loader'>,
@ -76,8 +77,8 @@ const getExceptionPage = exceptionPage => {
} }
}; };
const Content = ({ children, isLoading = false, loader, exceptionPage }: Props) => ( const Content = ({ className, children, isLoading = false, loader, exceptionPage }: Props) => (
<Wrapper> <Wrapper className={className}>
{!isLoading && children} {!isLoading && children}
{isLoading && exceptionPage && getExceptionPage(exceptionPage)} {isLoading && exceptionPage && getExceptionPage(exceptionPage)}
{isLoading && loader && ( {isLoading && loader && (
@ -98,6 +99,7 @@ const Content = ({ children, isLoading = false, loader, exceptionPage }: Props)
Content.propTypes = { Content.propTypes = {
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]),
className: PropTypes.string,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
loader: PropTypes.object, loader: PropTypes.object,
exceptionPage: PropTypes.object, exceptionPage: PropTypes.object,

View File

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
import { toggleDeviceDropdown } from 'actions/WalletActions'; import { toggleDeviceDropdown, toggleSidebar, setHideBalance } from 'actions/WalletActions';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
@ -42,6 +42,8 @@ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps>
gotoDeviceSettings: bindActionCreators(RouterActions.gotoDeviceSettings, dispatch), gotoDeviceSettings: bindActionCreators(RouterActions.gotoDeviceSettings, dispatch),
onSelectDevice: bindActionCreators(RouterActions.selectDevice, dispatch), onSelectDevice: bindActionCreators(RouterActions.selectDevice, dispatch),
gotoExternalWallet: bindActionCreators(ModalActions.gotoExternalWallet, dispatch), gotoExternalWallet: bindActionCreators(ModalActions.gotoExternalWallet, dispatch),
toggleSidebar: bindActionCreators(toggleSidebar, dispatch),
setHideBalance: bindActionCreators(setHideBalance, dispatch),
}); });
export default withRouter( export default withRouter(

View File

@ -141,7 +141,7 @@ const AccountMenu = (props: Props) => {
{...l10nCommonMessages.TR_ACCOUNT_HASH} {...l10nCommonMessages.TR_ACCOUNT_HASH}
values={{ number: account.index + 1 }} values={{ number: account.index + 1 }}
/> />
{balance && ( {balance && !props.wallet.hideBalance && (
<Text> <Text>
{balance} {balance}
{fiatRates && ( {fiatRates && (

View File

@ -3,6 +3,7 @@ import styled from 'styled-components';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import Link from 'components/Link'; import Link from 'components/Link';
import { Switch } from 'trezor-ui-components';
import DeviceIcon from 'components/images/DeviceIcon'; import DeviceIcon from 'components/images/DeviceIcon';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { getPattern } from 'support/routes'; import { getPattern } from 'support/routes';
@ -21,6 +22,7 @@ const Wrapper = styled.div`
const Item = styled.div` const Item = styled.div`
padding: 6px 24px; padding: 6px 24px;
display: flex; display: flex;
height: 38px;
align-items: center; align-items: center;
font-size: ${FONT_SIZE.BASE}; font-size: ${FONT_SIZE.BASE};
cursor: pointer; cursor: pointer;
@ -39,6 +41,7 @@ const Divider = styled.div`
const Label = styled.div` const Label = styled.div`
padding-left: 15px; padding-left: 15px;
flex: 1;
`; `;
class MenuItems extends PureComponent { class MenuItems extends PureComponent {
@ -108,6 +111,24 @@ class MenuItems extends PureComponent {
</Label> </Label>
</Item> </Item>
<Divider /> <Divider />
<Item>
<Icon icon={icons.EYE_CROSSED} size={25} color={colors.TEXT_SECONDARY} />
<Label>
<FormattedMessage {...l10nCommonMessages.TR_HIDE_BALANCE} />
</Label>
<Switch
width={36}
height={18}
handleDiameter={14}
checkedIcon={false}
uncheckedIcon={false}
onChange={checked => {
this.props.setHideBalance(checked);
}}
checked={this.props.wallet.hideBalance}
/>
</Item>
<Divider />
<Link to={getPattern('wallet-settings')}> <Link to={getPattern('wallet-settings')}>
<Item> <Item>
<Icon icon={icons.COG} size={25} color={colors.TEXT_SECONDARY} /> <Icon icon={icons.COG} size={25} color={colors.TEXT_SECONDARY} />
@ -123,9 +144,11 @@ class MenuItems extends PureComponent {
MenuItems.propTypes = { MenuItems.propTypes = {
device: PropTypes.object.isRequired, device: PropTypes.object.isRequired,
wallet: PropTypes.object.isRequired,
acquireDevice: PropTypes.func.isRequired, acquireDevice: PropTypes.func.isRequired,
forgetDevice: PropTypes.func.isRequired, forgetDevice: PropTypes.func.isRequired,
duplicateDevice: PropTypes.func.isRequired, duplicateDevice: PropTypes.func.isRequired,
setHideBalance: PropTypes.func.isRequired,
// toggleDeviceDropdown: PropTypes.func.isRequired, // toggleDeviceDropdown: PropTypes.func.isRequired,
// gotoDeviceSettings: PropTypes.func.isRequired, // gotoDeviceSettings: PropTypes.func.isRequired,
}; };

View File

@ -3,7 +3,7 @@ import * as TrezorConnectActions from 'actions/TrezorConnectActions';
import * as DiscoveryActions from 'actions/DiscoveryActions'; import * as DiscoveryActions from 'actions/DiscoveryActions';
import * as RouterActions from 'actions/RouterActions'; import * as RouterActions from 'actions/RouterActions';
import * as ModalActions from 'actions/ModalActions'; import * as ModalActions from 'actions/ModalActions';
import { toggleDeviceDropdown } from 'actions/WalletActions'; import * as WalletActions from 'actions/WalletActions';
import type { State } from 'flowtype'; import type { State } from 'flowtype';
export type StateProps = { export type StateProps = {
@ -19,7 +19,9 @@ export type StateProps = {
}; };
export type DispatchProps = { export type DispatchProps = {
toggleDeviceDropdown: typeof toggleDeviceDropdown, toggleDeviceDropdown: typeof WalletActions.toggleDeviceDropdown,
toggleSidebar: typeof WalletActions.toggleSidebar,
setHideBalance: typeof WalletActions.setHideBalance,
addAccount: typeof DiscoveryActions.addAccount, addAccount: typeof DiscoveryActions.addAccount,
acquireDevice: typeof TrezorConnectActions.acquire, acquireDevice: typeof TrezorConnectActions.acquire,
forgetDevice: typeof TrezorConnectActions.forget, forgetDevice: typeof TrezorConnectActions.forget,

View File

@ -3,13 +3,14 @@
import * as React from 'react'; import * as React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import colors from 'config/colors'; import colors from 'config/colors';
import { FONT_SIZE } from 'config/variables'; import { FONT_SIZE, SCREEN_SIZE } from 'config/variables';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import WalletTypeIcon from 'components/images/WalletType'; import WalletTypeIcon from 'components/images/WalletType';
import icons from 'config/icons'; import icons from 'config/icons';
import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { TransitionGroup, CSSTransition } from 'react-transition-group';
import styled from 'styled-components'; import styled from 'styled-components';
import DeviceHeader from 'components/DeviceHeader'; import DeviceHeader from 'components/DeviceHeader';
import Backdrop from 'components/Backdrop';
// import Link from 'components/Link'; // import Link from 'components/Link';
import * as deviceUtils from 'utils/device'; import * as deviceUtils from 'utils/device';
@ -98,6 +99,14 @@ type TransitionMenuProps = {
children?: React.Node, children?: React.Node,
}; };
const StyledBackdrop = styled(Backdrop)`
display: none;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: initial;
}
`;
// TransitionMenu needs to dispatch window.resize event // TransitionMenu needs to dispatch window.resize event
// in order to StickyContainer be recalculated // in order to StickyContainer be recalculated
const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => ( const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => (
@ -254,66 +263,74 @@ class LeftNavigation extends React.PureComponent<Props, State> {
selectedDevice && selectedDevice.connected && selectedDevice.available; selectedDevice && selectedDevice.connected && selectedDevice.available;
return ( return (
<Sidebar isOpen={props.wallet.showSidebar}> <>
<Header <StyledBackdrop
isSelected show={props.wallet.showSidebar}
testId="Main__page__device__header" onClick={props.toggleSidebar}
isHoverable={false} animated
onClickWrapper={() => { />
if (isDeviceAccessible || this.props.devices.length > 1) { <Sidebar isOpen={props.wallet.showSidebar}>
this.handleOpen(); <Header
} isSelected
}} testId="Main__page__device__header"
device={selectedDevice} isHoverable={false}
disabled={!isDeviceAccessible && this.props.devices.length === 1} onClickWrapper={() => {
isOpen={this.props.wallet.dropdownOpened} if (isDeviceAccessible || this.props.devices.length > 1) {
icon={ this.handleOpen();
<React.Fragment> }
{showWalletType && ( }}
<Tooltip device={selectedDevice}
content={ disabled={!isDeviceAccessible && this.props.devices.length === 1}
<WalletTooltipMsg isOpen={this.props.wallet.dropdownOpened}
walletType={walletType} icon={
isDeviceReady={isDeviceReady} <React.Fragment>
/> {showWalletType && (
} <Tooltip
maxWidth={200} content={
placement="bottom" <WalletTooltipMsg
enterDelayMs={0.5} walletType={walletType}
> isDeviceReady={isDeviceReady}
<WalletTypeIconWrapper> />
<WalletTypeIcon }
onClick={e => { maxWidth={200}
if (selectedDevice && isDeviceReady) { placement="bottom"
this.props.duplicateDevice(selectedDevice); enterDelayMs={0.5}
e.stopPropagation(); >
<WalletTypeIconWrapper>
<WalletTypeIcon
onClick={e => {
if (selectedDevice && isDeviceReady) {
this.props.duplicateDevice(selectedDevice);
e.stopPropagation();
}
}}
hoverColor={
isDeviceReady
? colors.TEXT_PRIMARY
: colors.TEXT_SECONDARY
} }
}} type={walletType}
hoverColor={ size={25}
isDeviceReady color={colors.TEXT_SECONDARY}
? colors.TEXT_PRIMARY />
: colors.TEXT_SECONDARY </WalletTypeIconWrapper>
} </Tooltip>
type={walletType} )}
size={25} {this.props.devices.length > 1 && (
color={colors.TEXT_SECONDARY} <Tooltip
/> content={
</WalletTypeIconWrapper> <FormattedMessage
</Tooltip> {...l10nMessages.TR_NUMBER_OF_DEVICES}
)} />
{this.props.devices.length > 1 && ( }
<Tooltip maxWidth={200}
content={ placement="bottom"
<FormattedMessage {...l10nMessages.TR_NUMBER_OF_DEVICES} /> enterDelayMs={0.5}
} >
maxWidth={200} <Counter>{this.props.devices.length}</Counter>
placement="bottom" </Tooltip>
enterDelayMs={0.5} )}
> {/* <Tooltip
<Counter>{this.props.devices.length}</Counter>
</Tooltip>
)}
{/* <Tooltip
content={ content={
<FormattedMessage <FormattedMessage
{...l10nCommonMessages.TR_APPLICATION_SETTINGS} {...l10nCommonMessages.TR_APPLICATION_SETTINGS}
@ -334,34 +351,35 @@ class LeftNavigation extends React.PureComponent<Props, State> {
</Link> </Link>
</WalletTypeIconWrapper> </WalletTypeIconWrapper>
</Tooltip> */} </Tooltip> */}
<Icon <Icon
canAnimate={this.state.clicked === true} canAnimate={this.state.clicked === true}
isActive={this.props.wallet.dropdownOpened} isActive={this.props.wallet.dropdownOpened}
size={25} size={25}
color={colors.TEXT_SECONDARY} color={colors.TEXT_SECONDARY}
icon={icons.ARROW_DOWN} icon={icons.ARROW_DOWN}
/> />
</React.Fragment> </React.Fragment>
} }
{...this.props} {...this.props}
/> />
<Body minHeight={this.state.bodyMinHeight}> <Body minHeight={this.state.bodyMinHeight}>
{dropdownOpened && <DeviceMenu ref={this.deviceMenuRef} {...this.props} />} {dropdownOpened && <DeviceMenu ref={this.deviceMenuRef} {...this.props} />}
{isDeviceAccessible && menu} {isDeviceAccessible && menu}
</Body> </Body>
<Footer data-test="Main__page__footer" key="sticky-footer"> <Footer data-test="Main__page__footer" key="sticky-footer">
<Help> <Help>
<A <A
href="https://trezor.io/support/" href="https://trezor.io/support/"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<Icon size={26} icon={icons.CHAT} color={colors.TEXT_SECONDARY} /> <Icon size={26} icon={icons.CHAT} color={colors.TEXT_SECONDARY} />
<FormattedMessage {...l10nMessages.TR_NEED_HELP} /> <FormattedMessage {...l10nMessages.TR_NEED_HELP} />
</A> </A>
</Help> </Help>
</Footer> </Footer>
</Sidebar> </Sidebar>
</>
); );
} }
} }
@ -383,6 +401,7 @@ LeftNavigation.propTypes = {
duplicateDevice: PropTypes.func, duplicateDevice: PropTypes.func,
gotoDeviceSettings: PropTypes.func, gotoDeviceSettings: PropTypes.func,
onSelectDevice: PropTypes.func, onSelectDevice: PropTypes.func,
setHideBalance: PropTypes.func,
}; };
export default LeftNavigation; export default LeftNavigation;

View File

@ -23,7 +23,6 @@ import ContextNotifications from 'components/notifications/Context';
import { SCREEN_SIZE } from 'config/variables'; import { SCREEN_SIZE } from 'config/variables';
import Log from 'components/Log'; import Log from 'components/Log';
import Backdrop from 'components/Backdrop';
import LeftNavigation from './components/LeftNavigation/Container'; import LeftNavigation from './components/LeftNavigation/Container';
import TopNavigationAccount from './components/TopNavigationAccount'; import TopNavigationAccount from './components/TopNavigationAccount';
@ -110,14 +109,6 @@ const Body = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const StyledBackdrop = styled(Backdrop)`
display: none;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: initial;
}
`;
const Wallet = (props: Props) => ( const Wallet = (props: Props) => (
<AppWrapper> <AppWrapper>
<Header <Header
@ -127,11 +118,6 @@ const Wallet = (props: Props) => (
/> />
<AppNotifications /> <AppNotifications />
<WalletWrapper> <WalletWrapper>
<StyledBackdrop
show={props.wallet.showSidebar}
onClick={props.toggleSidebar}
animated
/>
{props.wallet.selectedDevice && <LeftNavigation />} {props.wallet.selectedDevice && <LeftNavigation />}
<MainContent preventBgScroll={props.wallet.showSidebar}> <MainContent preventBgScroll={props.wallet.showSidebar}>
<Navigation> <Navigation>

View File

@ -406,7 +406,7 @@ const AccountSend = (props: Props) => {
<AmountInputLabel> <AmountInputLabel>
<FormattedMessage {...l10nSendMessages.TR_AMOUNT} /> <FormattedMessage {...l10nSendMessages.TR_AMOUNT} />
</AmountInputLabel> </AmountInputLabel>
{isCurrentCurrencyToken && selectedToken && ( {isCurrentCurrencyToken && selectedToken && !props.wallet.hideBalance && (
<AmountInputLabel> <AmountInputLabel>
<FormattedMessage <FormattedMessage
{...l10nMessages.YOU_HAVE_TOKEN_BALANCE} {...l10nMessages.YOU_HAVE_TOKEN_BALANCE}

View File

@ -17,6 +17,7 @@ type Props = {
balance: string, balance: string,
fiat: $ElementType<ReducersState, 'fiat'>, fiat: $ElementType<ReducersState, 'fiat'>,
localCurrency: string, localCurrency: string,
isHidden: boolean,
}; };
type State = { type State = {
@ -105,11 +106,20 @@ class AccountBalance extends PureComponent<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
isHidden: false, isHidden: props.isHidden,
canAnimateHideBalanceIcon: false, canAnimateHideBalanceIcon: props.isHidden,
}; };
} }
componentDidUpdate(prevProps: Props) {
if (prevProps.isHidden !== this.props.isHidden) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
isHidden: this.props.isHidden,
});
}
}
handleHideBalanceIconClick() { handleHideBalanceIconClick() {
this.setState(previousState => ({ this.setState(previousState => ({
isHidden: !previousState.isHidden, isHidden: !previousState.isHidden,

View File

@ -1,15 +1,18 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import ColorHash from 'color-hash'; import ColorHash from 'color-hash';
import ScaleText from 'react-scale-text'; import ScaleText from 'react-scale-text';
import colors from 'config/colors'; import colors from 'config/colors';
import { FONT_WEIGHT } from 'config/variables'; import { FONT_WEIGHT } from 'config/variables';
import Button from 'components/Button'; import Button from 'components/Button';
import Tooltip from 'components/Tooltip';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ICONS from 'config/icons'; import ICONS from 'config/icons';
import * as stateUtils from 'reducers/utils'; import * as stateUtils from 'reducers/utils';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import l10nCommonMessages from 'views/common.messages';
const TokenWrapper = styled.div` const TokenWrapper = styled.div`
padding: 14px 0; padding: 14px 0;
@ -60,6 +63,10 @@ const RemoveTokenButton = styled(Button)`
padding: 0 0 0 10px; padding: 0 0 0 10px;
`; `;
const TooltipIcon = styled(Icon)`
cursor: pointer;
`;
class AddedToken extends PureComponent { class AddedToken extends PureComponent {
getTokenBalance(token) { getTokenBalance(token) {
const pendingAmount = stateUtils.getPendingAmount(this.props.pending, token.symbol, true); const pendingAmount = stateUtils.getPendingAmount(this.props.pending, token.symbol, true);
@ -84,7 +91,25 @@ class AddedToken extends PureComponent {
<TokenName>{this.props.token.name}</TokenName> <TokenName>{this.props.token.name}</TokenName>
<TokenBalance> <TokenBalance>
{this.getTokenBalance(this.props.token)} {this.props.token.symbol} {this.props.hideBalance ? (
<Tooltip
maxWidth={200}
placement="top"
content={
<FormattedMessage
{...l10nCommonMessages.TR_THE_ACCOUNT_BALANCE_IS_HIDDEN}
/>
}
>
<TooltipIcon
icon={ICONS.EYE_CROSSED}
size={25}
color={colors.TEXT_SECONDARY}
/>
</Tooltip>
) : (
`${this.getTokenBalance(this.props.token)} ${this.props.token.symbol}`
)}
</TokenBalance> </TokenBalance>
<RemoveTokenButton <RemoveTokenButton
isTransparent isTransparent
@ -101,6 +126,7 @@ AddedToken.propTypes = {
token: PropTypes.object, token: PropTypes.object,
pending: PropTypes.array, pending: PropTypes.array,
removeToken: PropTypes.func, removeToken: PropTypes.func,
hideBalance: PropTypes.bool,
}; };
export default AddedToken; export default AddedToken;

View File

@ -105,6 +105,7 @@ const AccountSummary = (props: Props) => {
balance={balance} balance={balance}
fiat={props.fiat} fiat={props.fiat}
localCurrency={props.wallet.localCurrency} localCurrency={props.wallet.localCurrency}
isHidden={props.wallet.hideBalance}
/> />
<H2Wrapper> <H2Wrapper>
<H2> <H2>
@ -164,6 +165,7 @@ const AccountSummary = (props: Props) => {
token={token} token={token}
pending={pending} pending={pending}
removeToken={props.removeToken} removeToken={props.removeToken}
hideBalance={props.wallet.hideBalance}
/> />
))} ))}
</AddedTokensWrapper> </AddedTokensWrapper>

View File

@ -17,6 +17,7 @@ type Props = {
reserve: string, reserve: string,
fiat: $ElementType<ReducersState, 'fiat'>, fiat: $ElementType<ReducersState, 'fiat'>,
localCurrency: string, localCurrency: string,
isHidden: boolean,
}; };
type State = { type State = {
@ -105,11 +106,21 @@ class AccountBalance extends PureComponent<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
isHidden: false, isHidden: props.isHidden,
canAnimateHideBalanceIcon: false, canAnimateHideBalanceIcon: props.isHidden,
}; };
} }
componentDidUpdate(prevProps: Props) {
console.log(this.props.isHidden);
if (prevProps.isHidden !== this.props.isHidden) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
isHidden: this.props.isHidden,
});
}
}
handleHideBalanceIconClick() { handleHideBalanceIconClick() {
this.setState(previousState => ({ this.setState(previousState => ({
isHidden: !previousState.isHidden, isHidden: !previousState.isHidden,

View File

@ -86,6 +86,7 @@ const AccountSummary = (props: Props) => {
reserve={reserve} reserve={reserve}
fiat={props.fiat} fiat={props.fiat}
localCurrency={props.wallet.localCurrency} localCurrency={props.wallet.localCurrency}
isHidden={props.wallet.hideBalance}
/> />
{TMP_SHOW_HISTORY && ( {TMP_SHOW_HISTORY && (
<H2Wrapper> <H2Wrapper>

View File

@ -18,6 +18,7 @@ type StateProps = {
type DispatchProps = { type DispatchProps = {
setLocalCurrency: typeof WalletActions.setLocalCurrency, setLocalCurrency: typeof WalletActions.setLocalCurrency,
setHideBalance: typeof WalletActions.setHideBalance,
}; };
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
@ -34,6 +35,7 @@ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps>
dispatch: Dispatch dispatch: Dispatch
): DispatchProps => ({ ): DispatchProps => ({
setLocalCurrency: bindActionCreators(WalletActions.setLocalCurrency, dispatch), setLocalCurrency: bindActionCreators(WalletActions.setLocalCurrency, dispatch),
setHideBalance: bindActionCreators(WalletActions.setHideBalance, dispatch),
}); });
export default injectIntl( export default injectIntl(

View File

@ -3,24 +3,38 @@ import styled from 'styled-components';
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Link from 'components/Link';
import Content from 'views/Wallet/components/Content'; import Content from 'views/Wallet/components/Content';
import { Select } from 'components/Select'; import {
import Button from 'components/Button'; Switch,
Select,
import colors from 'config/colors'; Link,
Button,
Tooltip,
Icon,
icons as ICONS,
colors,
} from 'trezor-ui-components';
import { FIAT_CURRENCIES } from 'config/app'; import { FIAT_CURRENCIES } from 'config/app';
import { FONT_SIZE } from 'config/variables'; import { FONT_SIZE } from 'config/variables';
import l10nCommonMessages from 'views/common.messages'; import l10nCommonMessages from 'views/common.messages';
import l10nMessages from './index.messages'; import l10nMessages from './index.messages';
import type { Props } from './Container'; import type { Props } from './Container';
const CurrencySelect = styled(Select)` const StyledContent = styled(Content)`
min-width: 77px; max-width: 800px;
/* max-width: 200px; */
`; `;
const CurrencyLabel = styled.div` const CurrencySelect = styled(Select)`
min-width: 77px;
`;
const Label = styled.div`
display: flex;
color: ${colors.TEXT_SECONDARY};
align-items: center;
`;
const LabelTop = styled.div`
color: ${colors.TEXT_SECONDARY}; color: ${colors.TEXT_SECONDARY};
padding-bottom: 10px; padding-bottom: 10px;
`; `;
@ -29,8 +43,15 @@ const Section = styled.div`
margin-bottom: 20px; margin-bottom: 20px;
`; `;
const Row = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;
const Actions = styled.div` const Actions = styled.div`
display: flex; display: flex;
margin-top: 40px;
`; `;
const Buttons = styled.div` const Buttons = styled.div`
@ -45,16 +66,20 @@ const Info = styled.div`
align-self: center; align-self: center;
`; `;
const TooltipIcon = styled(Icon)`
cursor: pointer;
`;
const buildCurrencyOption = currency => { const buildCurrencyOption = currency => {
return { value: currency, label: currency.toUpperCase() }; return { value: currency, label: currency.toUpperCase() };
}; };
const WalletSettings = (props: Props) => ( const WalletSettings = (props: Props) => (
<Content> <StyledContent>
<Section> <Section>
<CurrencyLabel> <LabelTop>
<FormattedMessage {...l10nMessages.TR_LOCAL_CURRENCY} /> <FormattedMessage {...l10nMessages.TR_LOCAL_CURRENCY} />
</CurrencyLabel> </LabelTop>
<CurrencySelect <CurrencySelect
isSearchable isSearchable
isClearable={false} isClearable={false}
@ -63,6 +88,26 @@ const WalletSettings = (props: Props) => (
options={FIAT_CURRENCIES.map(c => buildCurrencyOption(c))} options={FIAT_CURRENCIES.map(c => buildCurrencyOption(c))}
/> />
</Section> </Section>
<Section>
<Row>
<Label>
<FormattedMessage {...l10nCommonMessages.TR_HIDE_BALANCE} />
<Tooltip
content={<FormattedMessage {...l10nMessages.TR_HIDE_BALANCE_EXPLAINED} />}
maxWidth={210}
placement="right"
>
<TooltipIcon icon={ICONS.HELP} color={colors.TEXT_SECONDARY} size={24} />
</Tooltip>
</Label>
<Switch
onChange={checked => {
props.setHideBalance(checked);
}}
checked={props.wallet.hideBalance}
/>
</Row>
</Section>
<Actions> <Actions>
<Info> <Info>
<FormattedMessage {...l10nMessages.TR_THE_CHANGES_ARE_SAVED} /> <FormattedMessage {...l10nMessages.TR_THE_CHANGES_ARE_SAVED} />
@ -75,7 +120,7 @@ const WalletSettings = (props: Props) => (
</Link> </Link>
</Buttons> </Buttons>
</Actions> </Actions>
</Content> </StyledContent>
); );
export default WalletSettings; export default WalletSettings;

View File

@ -7,6 +7,11 @@ const definedMessages: Messages = defineMessages({
id: 'TR_LOCAL_CURRENCY', id: 'TR_LOCAL_CURRENCY',
defaultMessage: 'Local currency', defaultMessage: 'Local currency',
}, },
TR_HIDE_BALANCE_EXPLAINED: {
id: 'TR_HIDE_BALANCE_EXPLAINED',
defaultMessage:
"Hides your account balance so you don't have to worry about anyone looking over your shoulder.",
},
TR_THE_CHANGES_ARE_SAVED: { TR_THE_CHANGES_ARE_SAVED: {
id: 'TR_THE_CHANGES_ARE_SAVED', id: 'TR_THE_CHANGES_ARE_SAVED',
defaultMessage: 'The changes are saved automatically as they are made', defaultMessage: 'The changes are saved automatically as they are made',

View File

@ -70,6 +70,14 @@ const definedMessages: Messages = defineMessages({
id: 'TR_CLOSE', id: 'TR_CLOSE',
defaultMessage: 'Close', defaultMessage: 'Close',
}, },
TR_HIDE_BALANCE: {
id: 'TR_HIDE_BALANCE',
defaultMessage: 'Hide balance',
},
TR_THE_ACCOUNT_BALANCE_IS_HIDDEN: {
id: 'TR_THE_ACCOUNT_BALANCE_IS_HIDDEN',
defaultMessage: 'The account balance is hidden.',
},
}); });
export default definedMessages; export default definedMessages;

View File

@ -8844,6 +8844,15 @@ prop-types@^15.6.2:
loose-envify "^1.3.1" loose-envify "^1.3.1"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
protobufjs@^6.8.8: protobufjs@^6.8.8:
version "6.8.8" version "6.8.8"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c"
@ -9169,6 +9178,11 @@ react-is@^16.3.2, react-is@^16.6.0, react-is@^16.6.3:
version "16.6.3" version "16.6.3"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0"
react-is@^16.8.1:
version "16.8.4"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
react-json-view@^1.19.1: react-json-view@^1.19.1:
version "1.19.1" version "1.19.1"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c" resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
@ -9257,6 +9271,13 @@ react-select@^2.3.0:
react-input-autosize "^2.2.1" react-input-autosize "^2.2.1"
react-transition-group "^2.2.1" react-transition-group "^2.2.1"
react-switch@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/react-switch/-/react-switch-4.1.0.tgz#93379df044c9febe86d0b1485cb14b2a364562b6"
integrity sha512-mINolJx85ZojtsKdH+DE+p9i073a1mQscEADF6y/FLBshSmyciZffBWP0xu8jWyqRIg7tWykFLJt+2xlMya7sw==
dependencies:
prop-types "^15.6.2"
react-textarea-autosize@^6.1.0: react-textarea-autosize@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5" resolved "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
@ -11154,6 +11175,21 @@ trezor-translations-manager@^1.0.4:
request "^2.88.0" request "^2.88.0"
request-promise-native "^1.0.5" request-promise-native "^1.0.5"
trezor-ui-components@^1.0.0-beta.4:
version "1.0.0-beta.4"
resolved "https://registry.yarnpkg.com/trezor-ui-components/-/trezor-ui-components-1.0.0-beta.4.tgz#e68185775e13a130076e548a68ba61053f1053bf"
integrity sha512-ikH/k8p4rVonfDqqo8NjZPE3N73aq3P4lk6cvN1XwRSP70lsGELG5qoU71eQsThGsN1dioxhOxWA7HVAazHvDw==
dependencies:
prop-types "^15.7.2"
rc-tooltip "^3.7.3"
react "^16.8.1"
react-dom "^16.8.1"
react-router-dom "^4.3.1"
react-select "^2.3.0"
react-switch "^4.1.0"
react-textarea-autosize "^7.1.0"
styled-components "^4.1.3"
trim-newlines@^1.0.0: trim-newlines@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"