Commit 228b11d5 authored by Thibaut Sardan's avatar Thibaut Sardan Committed by Maciej Hirsz
Browse files

Menu refactoring using popup (#227)

* fix(): account menu

* fix(): handle account deletion

* fix(): add onboarding message when no account

* fix(): refactoring menu as component

* fix(): address comments
parent 90b6cdd4
Pipeline #34486 failed with stage
in 10 minutes and 1 second
......@@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
......
......@@ -12970,6 +12970,11 @@
"react-native-fit-image": "^1.5.2"
}
},
"react-native-popup-menu": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/react-native-popup-menu/-/react-native-popup-menu-0.15.0.tgz",
"integrity": "sha512-iJIzVNq46oCxKQVFw+4ceNwYDfbvbtn8nchSOluVB3NlPZKLCOZ+0u1KzA0fyxPtQdLf2yFbbVPi/A8XbP0eOg=="
},
"react-native-qrcode": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/react-native-qrcode/-/react-native-qrcode-0.2.7.tgz",
......@@ -13093,11 +13098,6 @@
"ansi-regex": "^3.0.0"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
},
"yargs": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz",
......@@ -14973,8 +14973,7 @@
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
},
"strip-eof": {
"version": "1.0.0",
......
......@@ -25,13 +25,14 @@ import {
withNavigation
} from 'react-navigation';
import { Provider as UnstatedProvider } from 'unstated';
import { MenuProvider } from 'react-native-popup-menu';
import '../ReactotronConfig';
import colors from './colors';
import Background from './components/Background';
import HeaderLeftHome from './components/HeaderLeftHome';
import SecurityHeader from './components/SecurityHeader';
import About from './screens/About';
import AccountAdd from './screens/AccountAdd';
import AccountBackup from './screens/AccountBackup';
import AccountDetails from './screens/AccountDetails';
import AccountEdit from './screens/AccountEdit';
......@@ -55,9 +56,11 @@ export default class App extends Component {
render() {
return (
<UnstatedProvider>
<StatusBar barStyle="light-content" />
<Background />
<Screens />
<MenuProvider backHandler={true}>
<StatusBar barStyle="light-content" />
<Background />
<Screens />
</MenuProvider>
</UnstatedProvider>
);
}
......@@ -156,12 +159,6 @@ const Screens = createStackNavigator(
headerLeft: <HeaderLeftHome />
}
},
AccountAdd: {
screen: AccountAdd,
navigationOptions: {
headerLeft: <HeaderLeftHome />
}
},
AccountNetworkChooser: {
screen: AccountNetworkChooser
},
......
......@@ -142,9 +142,7 @@ export default class AccountSeed extends Component {
}
renderSuggestions() {
const { value, valid } = this.props;
const invalidStyles = !valid ? styles.invalidInput : {};
const { value } = this.props;
const words = value.length ? value.split(' ') : [];
const wordPosition = this.getWordPosition();
let searchInput = this.getSearchInput();
......@@ -163,6 +161,7 @@ export default class AccountSeed extends Component {
: {};
return (
<TouchableItem
key={i}
onPress={e => {
words[wordPosition] = suggestion;
this.props.onChangeText(words.join(' '));
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
'use strict';
import React from 'react';
import { Text } from 'react-native';
import {
Menu,
MenuOptions,
MenuOption,
MenuTrigger,
} from 'react-native-popup-menu';
import Icon from 'react-native-vector-icons/MaterialIcons';
import colors from '../colors';
export default class PopupMenu extends React.PureComponent {
render() {
const { onSelect, menuTriggerIconName, menuItems } = this.props
const menuTriggerIcon = <Icon name={menuTriggerIconName} size={35} color={colors.bg_text_sec} />
return (
<Menu onSelect={onSelect}>
<MenuTrigger children={menuTriggerIcon} />
<MenuOptions customStyles={menuOptionsStyles}>
{
menuItems.map((menuItem, index) => (
<MenuOption key={index} value={menuItem.value} >
<Text style={(menuItem.textStyle) ? menuItem.textStyle : null} >{menuItem.text}</Text>
</MenuOption>
))
}
</MenuOptions>
</Menu>
);
}
}
const menuOptionsStyles = {
optionWrapper: {
padding: 15,
},
optionText: {
fontFamily: 'Roboto',
fontSize: 16
},
};
......@@ -27,8 +27,6 @@ export default class About extends React.PureComponent {
};
render() {
const { navigation } = this.props;
const isWelcome = navigation.getParam('isWelcome');
return (
<ScrollView style={styles.body} contentContainerStyle={{ padding: 20 }}>
<Text style={styles.title}>PARITY SIGNER (v2.0-beta)</Text>
......
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
'use strict';
import React from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { Subscribe } from 'unstated';
import colors from '../colors';
import Background from '../components/Background';
import TouchableItem from '../components/TouchableItem';
import AccountsStore from '../stores/AccountsStore';
export default class AccountAdd extends React.PureComponent {
static navigationOptions = {
title: 'Add Account',
headerBackTitle: 'Back'
};
render() {
return (
<Subscribe to={[AccountsStore]}>
{accounts => <AccountAddView {...this.props} accounts={accounts} />}
</Subscribe>
);
}
}
class AccountAddView extends React.PureComponent {
componentWillMount() {
this.props.navigation.addListener('willFocus', () => {
this.props.accounts.resetNew();
});
}
render() {
const { navigation } = this.props;
const isWelcome = navigation.getParam('isWelcome');
return (
<ScrollView style={styles.body} contentContainerStyle={{ padding: 20 }}>
<Background />
{isWelcome && <Text style={styles.titleTop}>GETTING STARTED</Text>}
<TouchableItem
style={styles.card}
onPress={() => navigation.navigate('AccountNew', { isWelcome })}
>
<Icon
style={{
textAlign: 'center',
color: colors.card_text,
fontSize: 66
}}
name="layers"
/>
<Text style={[styles.cardText, { marginTop: 20 }]}>
Create New Account
</Text>
</TouchableItem>
<TouchableItem
style={[styles.card, { marginTop: 20 }]}
onPress={() => navigation.navigate('AccountRecover', { isWelcome })}
>
<Icon
style={{
textAlign: 'center',
color: colors.card_text,
fontSize: 66
}}
name="graphic-eq"
/>
<Text style={[styles.cardText, { marginTop: 20 }]}>
Recover Account
</Text>
</TouchableItem>
<TouchableItem
style={[styles.card, { marginTop: 20 }]}
onPress={() => navigation.navigate('About')}
>
<Text style={styles.cardText}>About</Text>
</TouchableItem>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
body: {
flex: 1,
flexDirection: 'column',
overflow: 'hidden',
backgroundColor: colors.bg
},
top: {
flex: 1
},
bottom: {
flexBasis: 50,
paddingBottom: 15
},
titleTop: {
color: colors.bg_text_sec,
fontSize: 24,
fontFamily: 'Manifold CF',
fontWeight: 'bold',
paddingBottom: 20,
textAlign: 'center'
},
card: {
backgroundColor: colors.card_bg,
padding: 20
},
cardText: {
textAlign: 'center',
color: colors.card_text,
fontFamily: 'Manifold CF',
fontSize: 20,
fontWeight: 'bold'
}
});
......@@ -80,16 +80,16 @@ class AccountBackupView extends React.PureComponent {
chainId={selected.chainId}
title={selected.name}
/>
<Text style={styles.titleTop}>RECOVERY WORDS</Text>
<Text style={styles.titleTop}>RECOVERY PHRASE</Text>
<Text style={styles.hintText}>
Write these words down on paper. Keep it safe. These words allow
anyone to recover this account.
anyone to recover and access the funds of this account.
</Text>
<TouchableItem
onPress={() => {
Alert.alert(
'Use paper to store seed phrases',
`It's not recommended to transfer or store seed phrases digitally and unencrypted. Everyone who have the phrase is able to spend funds from this account.
'Write this recovery phrase on paper',
`It is not recommended to transfer or store a recovery phrase digitally and unencrypted. Anyone in possession of this recovery phrase is able to spend funds from this account.
`,
[
{
......@@ -119,61 +119,20 @@ class AccountBackupView extends React.PureComponent {
{selected.seed}
</Text>
</TouchableItem>
<Button
buttonStyles={[styles.nextStep, { marginBottom: 20 }]}
title="Done Backup"
onPress={() => {
if (isNew) {
Alert.alert(
'Important information',
"Make sure you've backed up recovery words for your account. Recovery words are the only way to restore access to your account in case of device failure/lost.",
[
{
text: 'Proceed',
onPress: () => {
this.props.navigation.navigate('AccountPin', {
isWelcome: navigation.getParam('isWelcome'),
isNew
});
}
},
{
text: 'Cancel',
style: 'cancel'
}
]
);
} else {
navigation.navigate('AccountList');
}
}}
/>
{!isNew && (
{(isNew) &&
<Button
buttonStyles={{ marginBottom: 40 }}
title="Change PIN"
buttonStyles={[styles.nextStep, { marginBottom: 20 }]}
title="Backup Done"
onPress={() => {
navigation.navigate('AccountPin', { isNew });
}}
/>
)}
{!isNew && (
<Button
buttonStyles={styles.deleteButton}
title="Delete Account"
onPress={() => {
Alert.alert(
'Delete Account',
`Are you sure to delete ${selected.name || selected.address} and its private key?`,
'Important',
"Make sure you've backed up this recovery phrase. It is the only way to restore your account in case of device failure/lost.",
[
{
text: 'Delete',
style: 'destructive',
text: 'Proceed',
onPress: () => {
accounts.deleteAccount(selected);
this.props.navigation.navigate('AccountList');
this.props.navigation.navigate('AccountPin', { isNew });
}
},
{
......@@ -184,7 +143,7 @@ class AccountBackupView extends React.PureComponent {
);
}}
/>
)}
}
</ScrollView>
);
}
......
......@@ -17,14 +17,16 @@
'use strict';
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import { Alert, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import colors from '../colors';
import AccountDetailsCard from '../components/AccountDetailsCard';
import AccountCard from '../components/AccountCard';
import QrView from '../components/QrView';
import AccountsStore from '../stores/AccountsStore';
import TxStore from '../stores/TxStore';
import { accountId } from '../util/account';
import PopupMenu from '../components/PopupMenu'
export default class AccountDetails extends React.Component {
static navigationOptions = {
......@@ -58,10 +60,48 @@ class AccountDetailsView extends React.Component {
});
}
onDelete = () => {
const accounts = this.props.accounts
const selected = accounts.getSelected();
Alert.alert(
'Delete Account',
`Do you really want to delete ${selected.name || selected.address}?
This account can only be recovered with its associated recovery phrase.`,
[
{
text: 'Delete',
style: 'destructive',
onPress: () => {
accounts.deleteAccount(selected);
this.props.navigation.navigate('AccountList');
}
},
{
text: 'Cancel',
style: 'cancel'
}
]
);
}
componentWillUnmount() {
this.subscription.remove();
}
onOptionSelect = (value) => {
const navigate = this.props.navigation.navigate
if (value !== 'AccountEdit') {
navigate('AccountUnlock', {
next: value,
onDelete: this.onDelete
});
} else {
navigate(value);
}
}
render() {
const account = this.props.accounts.getSelected();
if (!account) {
......@@ -73,12 +113,24 @@ class AccountDetailsView extends React.Component {
contentContainerStyle={styles.bodyContent}
style={styles.body}
>
<Text style={styles.title}>ACCOUNT</Text>
<AccountDetailsCard
<View style={styles.header}>
<Text style={styles.title}>ACCOUNT</Text>
<View style={styles.menuView}>
<PopupMenu
onSelect={this.onOptionSelect}
menuTriggerIconName={"more-vert"}
menuItems={[
{ value: 'AccountEdit', text: 'Edit' },
{ value: 'AccountPin', text: 'Change Pin' },
{ value: 'AccountBackup', text: 'View Recovery Phrase' },
{ value: 'AccountDelete', text: 'Delete', textStyle: styles.deleteText }]}
/>
</View>
</View>
<AccountCard
address={account.address}
chainId={account.chainId}
title={account.name}
onPress={() => this.props.navigation.navigate('AccountEdit')}
/>
<View style={styles.qr}>
<QrView text={accountId(account)} />
......@@ -98,40 +150,30 @@ const styles = StyleSheet.create({
bodyContent: {
paddingBottom: 40
},
title: {
color: colors.bg_text_sec,
fontSize: 18,
fontFamily: 'Manifold CF',
fontWeight: 'bold',
paddingBottom: 20
},
wrapper: {
borderRadius: 5
},
address: {
flex: 1
},
qr: {
marginTop: 20,
backgroundColor: colors.card_bg
},
qrButton: {
marginTop: 20,
backgroundColor: colors.card_bg
},
deleteText: {
fontFamily: 'Manifold CF',
textAlign: 'right'
color: 'red'
},
changePinText: {
textAlign: 'left',
color: 'green'
header: {
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 20,
justifyContent: 'center',
},
actionsContainer: {
menuView: {
flex: 1,
flexDirection: 'row'
alignItems: 'flex-end',
},
actionButtonContainer: {
flex: 1
title: {
color: colors.bg_text_sec,
fontSize: 18,
fontFamily: 'Manifold CF',
fontWeight: 'bold',
flexDirection: 'column',
justifyContent: 'center',
}
});
......@@ -63,13 +63,7 @@ export default class AccountEdit extends React.PureComponent {
onChangeText={name => accounts.updateSelected({ name })}
onEndEditing={text => accounts.saveSelected()}
value={selected.name}
placeholder="Enter a new account name"
/>
<Button
title="Manage/Backup Account"
onPress={() => {
this.props.navigation.navigate('AccountUnlock');
}}
placeholder="New name"
/>
</ScrollView>
);
......
......@@ -18,19 +18,23 @@
import PropTypes from 'prop-types';
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { FlatList, Image, StyleSheet, Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import colors from '../colors';
import AccountCard from '../components/AccountCard';
import Background from '../components/Background';
import Button from '../components/Button';
import AccountsStore from '../stores/AccountsStore';
import { accountId } from '../util/account';
import PopupMenu from '../components/PopupMenu';