diff --git a/.prettierignore b/.prettierignore index 569d0b9b55d..c6393d47f6f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,4 @@ ios node_modules package-lock.json package.json +app/util/blockies.js diff --git a/android/app/build.gradle b/android/app/build.gradle index 9ea5b30d814..e256c0ba175 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -138,6 +138,7 @@ android { dependencies { compile project(':react-native-os') + compile project(':react-native-linear-gradient') compile project(':react-native-randombytes') compile project(':react-native-fs') compile project(':react-native-vector-icons') diff --git a/android/app/src/main/assets/fonts/Metamask.ttf b/android/app/src/main/assets/fonts/Metamask.ttf new file mode 100755 index 00000000000..6200158d790 Binary files /dev/null and b/android/app/src/main/assets/fonts/Metamask.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Black.ttf b/android/app/src/main/assets/fonts/Roboto-Black.ttf new file mode 100755 index 00000000000..689fe5cb3c7 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Black.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-BlackItalic.ttf b/android/app/src/main/assets/fonts/Roboto-BlackItalic.ttf new file mode 100755 index 00000000000..0b4e0ee1088 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-BlackItalic.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Bold.ttf b/android/app/src/main/assets/fonts/Roboto-Bold.ttf new file mode 100755 index 00000000000..d3f01ad245b Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Bold.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-BoldItalic.ttf b/android/app/src/main/assets/fonts/Roboto-BoldItalic.ttf new file mode 100755 index 00000000000..41cc1e75315 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-BoldItalic.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Italic.ttf b/android/app/src/main/assets/fonts/Roboto-Italic.ttf new file mode 100755 index 00000000000..6a1cee5b294 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Italic.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Light.ttf b/android/app/src/main/assets/fonts/Roboto-Light.ttf new file mode 100755 index 00000000000..219063a578a Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Light.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-LightItalic.ttf b/android/app/src/main/assets/fonts/Roboto-LightItalic.ttf new file mode 100755 index 00000000000..0e81e876fca Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-LightItalic.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Medium.ttf b/android/app/src/main/assets/fonts/Roboto-Medium.ttf new file mode 100755 index 00000000000..1a7f3b0bba4 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Medium.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-MediumItalic.ttf b/android/app/src/main/assets/fonts/Roboto-MediumItalic.ttf new file mode 100755 index 00000000000..003029527cc Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-MediumItalic.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Regular.ttf b/android/app/src/main/assets/fonts/Roboto-Regular.ttf new file mode 100755 index 00000000000..2c97eeadffe Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Regular.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Thin.ttf b/android/app/src/main/assets/fonts/Roboto-Thin.ttf new file mode 100755 index 00000000000..b74a4fd1a2e Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Thin.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-ThinItalic.ttf b/android/app/src/main/assets/fonts/Roboto-ThinItalic.ttf new file mode 100755 index 00000000000..dd0ddb85264 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-ThinItalic.ttf differ diff --git a/android/app/src/main/java/com/metamask/MainApplication.java b/android/app/src/main/java/com/metamask/MainApplication.java index fa002d4035e..59920198f10 100644 --- a/android/app/src/main/java/com/metamask/MainApplication.java +++ b/android/app/src/main/java/com/metamask/MainApplication.java @@ -4,6 +4,7 @@ import com.facebook.react.ReactApplication; import com.peel.react.rnos.RNOSModule; +import com.BV.LinearGradient.LinearGradientPackage; import com.bitgo.randombytes.RandomBytesPackage; import com.metamask.CustomWebview.CustomWebviewPackage; import com.rnfs.RNFSPackage; @@ -29,6 +30,7 @@ protected List getPackages() { return Arrays.asList( new MainReactPackage(), new RNOSModule(), + new LinearGradientPackage(), new RandomBytesPackage(), new RNFSPackage(), new VectorIconsPackage(), diff --git a/android/settings.gradle b/android/settings.gradle index 59a6baf5aeb..94b9dfc162b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,8 @@ rootProject.name = 'MetaMask' include ':react-native-os' project(':react-native-os').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-os/android') +include ':react-native-linear-gradient' +project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android') include ':react-native-randombytes' project(':react-native-randombytes').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-randombytes/android') include ':react-native-fs' diff --git a/app/components/AccountList/__snapshots__/index.test.js.snap b/app/components/AccountList/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..73c09de5980 --- /dev/null +++ b/app/components/AccountList/__snapshots__/index.test.js.snap @@ -0,0 +1,154 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Accounts should render correctly 1`] = ` + + + My Accounts + + + + + + + + + + Account 1 + + + 0.017700 + ETH + + + + + + + + + Account 2 + + + 0.4 + ETH + + + + + +`; diff --git a/app/components/AccountList/index.js b/app/components/AccountList/index.js new file mode 100644 index 00000000000..8716bd3724a --- /dev/null +++ b/app/components/AccountList/index.js @@ -0,0 +1,102 @@ +import React, { Component } from 'react'; +import { TouchableOpacity, StyleSheet, Text, View, SafeAreaView } from 'react-native'; +import Icon from 'react-native-vector-icons/Feather'; +import { colors, fontStyles } from '../../styles/common'; +import Identicon from '../Identicon'; + +const styles = StyleSheet.create({ + wrapper: { + backgroundColor: colors.concrete, + flex: 1 + }, + title: { + fontSize: 25, + marginVertical: 30, + marginHorizontal: 20, + color: colors.fontPrimary, + ...fontStyles.bold + }, + account: { + flexDirection: 'row', + marginLeft: 20, + marginBottom: 20 + }, + accountInfo: { + marginLeft: 15 + }, + accountLabel: { + fontSize: 18, + color: colors.fontPrimary, + ...fontStyles.normal + }, + accountBalance: { + paddingTop: 5, + fontSize: 12, + color: colors.fontSecondary, + ...fontStyles.normal + }, + selected: { + width: 30, + marginRight: 15 + } +}); + +/** + * View contains the list of all the available accounts + */ + +export default class AccountList extends Component { + state = { + selectedAccountIndex: 0 + }; + + onAccountPress = newIndex => { + this.setState({ selectedAccountIndex: newIndex }); + }; + + renderAccounts() { + const accounts = [ + { + label: 'Account 1', + address: '0xe7E125654064EEa56229f273dA586F10DF96B0a1', + balance: '0.017700' + }, + { + label: 'Account 2', + address: '0xf4F6A83117a9D0a9cA3b9684DEDaBc69d56721D8', + balance: '0.4' + } + ]; + + return accounts.map((account, i) => { + const selected = + this.state.selectedAccountIndex === i ? : null; + + const { address, label, balance } = account; + + return ( + this.onAccountPress(i)} // eslint-disable-line + > + {selected} + + + {label} + {balance} ETH + + + ); + }); + } + + render() { + return ( + + My Accounts + {this.renderAccounts()} + + ); + } +} diff --git a/app/components/AccountList/index.test.js b/app/components/AccountList/index.test.js new file mode 100644 index 00000000000..3af8ed5ac3d --- /dev/null +++ b/app/components/AccountList/index.test.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Accounts from './'; + +describe('Accounts', () => { + it('should render correctly', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/app/components/AccountOverview/__snapshots__/index.test.js.snap b/app/components/AccountOverview/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..5cb6b8828df --- /dev/null +++ b/app/components/AccountOverview/__snapshots__/index.test.js.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AccountOverview should render correctly 1`] = ` + + + + + Account 1 + + + $ + 1604.2 + + + + + + 0xe7...B0a1 + + + + +`; diff --git a/app/components/AccountOverview/index.js b/app/components/AccountOverview/index.js new file mode 100644 index 00000000000..da4e5bc6a1d --- /dev/null +++ b/app/components/AccountOverview/index.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import PropTypes from 'prop-types'; +import QRCode from 'react-native-qrcode'; +import { colors, fontStyles } from '../../styles/common'; + +const styles = StyleSheet.create({ + wrapper: { + backgroundColor: colors.white, + height: 130, + paddingTop: 20, + paddingHorizontal: 20, + paddingBottom: 0 + }, + row: { + flex: 1, + flexDirection: 'row' + }, + left: { + flex: 1 + }, + right: { + flex: 1, + alignItems: 'flex-end', + paddingTop: 5 + }, + + label: { + paddingTop: 7, + fontSize: 20, + ...fontStyles.normal + }, + amountFiat: { + flex: 1, + fontSize: 43, + lineHeight: 43, + paddingTop: 15, + color: colors.fontPrimary, + ...fontStyles.normal + }, + address: { + flex: 1, + marginTop: 10, + fontSize: 12, + ...fontStyles.normal + } +}); + +/** + * View that's part of the component + * which shows information about the selected account + */ + +export default class AccountOverview extends Component { + static propTypes = { + /** + * Object that represents the selected account + */ + account: PropTypes.object + }; + onDeposit = () => true; + onSend = () => true; + + render() { + const { + account: { label, balanceFiat, address } + } = this.props; + + return ( + + + + {label} + ${balanceFiat} + + + + {`${address.substr(0, 4)}...${address.substr(-4)}`} + + + + ); + } +} diff --git a/app/components/AccountOverview/index.test.js b/app/components/AccountOverview/index.test.js new file mode 100644 index 00000000000..e1e457e9247 --- /dev/null +++ b/app/components/AccountOverview/index.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import AccountOverview from './'; + +describe('AccountOverview', () => { + it('should render correctly', () => { + const account = { + address: '0xe7E125654064EEa56229f273dA586F10DF96B0a1', + balanceFiat: 1604.2, + label: 'Account 1' + }; + + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/app/components/App/__snapshots__/index.test.js.snap b/app/components/App/__snapshots__/index.test.js.snap index 1c3695af777..fe63b98b49d 100644 --- a/app/components/App/__snapshots__/index.test.js.snap +++ b/app/components/App/__snapshots__/index.test.js.snap @@ -4,8 +4,13 @@ exports[`App should render correctly 1`] = ` diff --git a/app/components/App/index.js b/app/components/App/index.js index 4b06f0c392c..b6d144ebdbf 100644 --- a/app/components/App/index.js +++ b/app/components/App/index.js @@ -1,49 +1,19 @@ -import React from 'react'; -import { StyleSheet } from 'react-native'; -import { createBottomTabNavigator } from 'react-navigation'; -import { createIconSetFromFontello } from 'react-native-vector-icons'; -import BrowserScreen from '../BrowserScreen'; -import Engine from '../../core/Engine'; -import WalletScreen from '../WalletScreen'; -import fontelloConfig from '../../fonts/config.json'; -import { colors } from '../../styles/common'; - -const Icon = createIconSetFromFontello(fontelloConfig); - -const engine = new Engine(); +import { createDrawerNavigator } from 'react-navigation'; +import Main from '../Main'; +import AccountList from '../AccountList'; /** - * Root application component responsible for configuring app navigation - * and instantiating the core Engine module + * Root application component responsible for instantiating + * the two top level views: Main and AccountList */ -export default createBottomTabNavigator( + +export default createDrawerNavigator( { - Home: { - screen: function Home() { - return ; - }, - navigationOptions: () => ({ - title: 'ÐApps', - tabBarIcon: ico => // eslint-disable-line react/display-name - }) - }, - Wallet: { - screen: function Home() { - return ; - }, - navigationOptions: () => ({ - title: 'Wallet', - tabBarIcon: ico => // eslint-disable-line react/display-name - }) + Main: { + screen: Main } }, { - tabBarOptions: { - activeTintColor: colors.primary, - inactiveTintColor: colors.inactive, - style: { - borderTopWidth: StyleSheet.hairlineWidth - } - } + contentComponent: AccountList } ); diff --git a/app/components/Asset/__snapshots__/index.test.js.snap b/app/components/Asset/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..fa1fc1f972c --- /dev/null +++ b/app/components/Asset/__snapshots__/index.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Asset should render correctly 1`] = ` + + + + + + + + +`; diff --git a/app/components/Asset/index.js b/app/components/Asset/index.js new file mode 100644 index 00000000000..26212e13efa --- /dev/null +++ b/app/components/Asset/index.js @@ -0,0 +1,59 @@ +import React, { Component } from 'react'; +import { ScrollView, View, StyleSheet } from 'react-native'; +import PropTypes from 'prop-types'; +import { colors, fontStyles } from '../../styles/common'; +import AssetOverview from '../AssetOverview'; +import Transactions from '../Transactions'; + +const styles = StyleSheet.create({ + wrapper: { + backgroundColor: colors.slate, + flex: 1 + }, + assetOverviewWrapper: { + height: 280 + } +}); + +/** + * View that displays a specific asset (Token or ETH) + * including the overview (Amount, Balance, Symbol, Logo) + * and also the transaction list + */ + +export default class Asset extends Component { + static propTypes = { + /** + /* navigation object required to access the props + /* passed by the parent component + */ + navigation: PropTypes.object + }; + + static navigationOptions = ({ navigation }) => ({ + title: `${navigation.getParam('name', '')} (${navigation.getParam('symbol', '')})`, + headerTitleStyle: { + fontSize: 20, + ...fontStyles.normal + } + }); + + render() { + const { + navigation: { + state: { params } + }, + navigation + } = this.props; + return ( + + + + + + + + + ); + } +} diff --git a/app/components/Asset/index.test.js b/app/components/Asset/index.test.js new file mode 100644 index 00000000000..6c7c40850fd --- /dev/null +++ b/app/components/Asset/index.test.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Asset from './'; + +describe('Asset', () => { + it('should render correctly', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/app/components/AssetOverview/__snapshots__/index.test.js.snap b/app/components/AssetOverview/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..8236f5caefb --- /dev/null +++ b/app/components/AssetOverview/__snapshots__/index.test.js.snap @@ -0,0 +1,164 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AssetOverview should render correctly 1`] = ` + + + + + + + + 4 + + ETH + + + $ + 1500 + USD + + + + + + + +`; diff --git a/app/components/AssetOverview/index.js b/app/components/AssetOverview/index.js new file mode 100644 index 00000000000..a6c2eba4970 --- /dev/null +++ b/app/components/AssetOverview/index.js @@ -0,0 +1,113 @@ +import React, { Component } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import PropTypes from 'prop-types'; +import Image from 'react-native-remote-svg'; +import LinearGradient from 'react-native-linear-gradient'; +import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; +import FoundationIcon from 'react-native-vector-icons/Foundation'; +import Button from '../Button'; +import { colors, fontStyles } from '../../styles/common'; + +const styles = StyleSheet.create({ + wrapper: { + flex: 1, + padding: 20, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.borderColor + }, + assetLogo: { + marginTop: 15, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 10, + marginBottom: 10 + }, + assetLogoImage: { + width: 60, + height: 60 + }, + balance: { + flex: 1, + alignItems: 'center', + marginTop: 10, + marginBottom: 10 + }, + buttons: { + flex: 1, + flexDirection: 'row', + marginTop: 30 + }, + amount: { + fontSize: 30, + color: colors.fontPrimary, + ...fontStyles.normal + }, + amountFiat: { + fontSize: 18, + color: colors.fontSecondary, + ...fontStyles.light + }, + button: { + flex: 1, + flexDirection: 'row' + }, + leftButton: { + marginRight: 10 + }, + rightButton: { + marginLeft: 10 + }, + buttonText: { + marginLeft: 8, + fontSize: 15, + color: colors.white, + ...fontStyles.bold + } +}); + +/** + * View that displays the information of a specific asset (Token or ETH) + * including the overview (Amount, Balance, Symbol, Logo) + */ + +export default class AssetOverview extends Component { + static propTypes = { + /** + * Object that represents the asset to be displayed + */ + asset: PropTypes.object + }; + onDeposit = () => true; + onSend = () => true; + + render() { + const { + asset: { logo, symbol, balance, balanceFiat } + } = this.props; + + return ( + + + + + + + {' '} + {balance} {symbol} + + ${balanceFiat} USD + + + + + + + ); + } +} diff --git a/app/components/AssetOverview/index.test.js b/app/components/AssetOverview/index.test.js new file mode 100644 index 00000000000..754399b0f58 --- /dev/null +++ b/app/components/AssetOverview/index.test.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import AssetOverview from './'; + +describe('AssetOverview', () => { + it('should render correctly', () => { + const asset = { + balance: 4, + balanceFiat: 1500, + logo: 'https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg', + symbol: 'ETH', + name: 'Ethereum' + }; + + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/app/components/Browser/index.js b/app/components/Browser/index.js index 757706fb0eb..87f23a72bec 100644 --- a/app/components/Browser/index.js +++ b/app/components/Browser/index.js @@ -6,6 +6,7 @@ import CustomWebview from '../CustomWebview'; // eslint-disable-line import/no-u import { Alert, Platform, StyleSheet, TextInput, View } from 'react-native'; import { colors, baseStyles } from '../../styles/common'; import BackgroundBridge from '../../core/BackgroundBridge'; +import Engine from '../../core/Engine'; const styles = StyleSheet.create({ urlBar: { @@ -51,11 +52,7 @@ export default class Browser extends Component { /** * Initial URL to load in the WebView */ - defaultURL: PropTypes.string.isRequired, - /** - * Instance of a core engine object - */ - engine: PropTypes.object.isRequired + defaultURL: PropTypes.string.isRequired }; state = { @@ -75,7 +72,7 @@ export default class Browser extends Component { webview = React.createRef(); async componentDidMount() { - this.backgroundBridge = new BackgroundBridge(this.props.engine, this.webview); + this.backgroundBridge = new BackgroundBridge(Engine, this.webview); // TODO: The presence of these async statement breaks Jest code coverage const entryScript = diff --git a/app/components/BrowserScreen/index.js b/app/components/BrowserScreen/index.js index b17ffd2e66a..501373bd41b 100644 --- a/app/components/BrowserScreen/index.js +++ b/app/components/BrowserScreen/index.js @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import Browser from '../Browser'; import Screen from '../Screen'; @@ -7,17 +6,10 @@ import Screen from '../Screen'; * Main view component for the browser screen */ export default class BrowserScreen extends Component { - static propTypes = { - /** - * Instance of a core engine object - */ - engine: PropTypes.object.isRequired - }; - render() { return ( - + ); } diff --git a/app/components/Button/__snapshots__/index.test.js.snap b/app/components/Button/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..aa805125f1d --- /dev/null +++ b/app/components/Button/__snapshots__/index.test.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Button should render correctly 1`] = ` + +`; diff --git a/app/components/Button/index.js b/app/components/Button/index.js new file mode 100644 index 00000000000..70e8fc7b1e3 --- /dev/null +++ b/app/components/Button/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ViewPropTypes, StyleSheet } from 'react-native'; +import GenericButton from '../GenericButton'; // eslint-disable-line import/no-unresolved +import { colors } from '../../styles/common'; + +const styles = StyleSheet.create({ + button: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.primary, + paddingVertical: 10, + paddingHorizontal: 15, + height: 40, + borderRadius: 4 + } +}); + +/** + * UI component that wraps GenericButton + * which renders the appropiate UI elements for each platform (android & iOS) + */ + +const Button = props => {props.children}; + +Button.propTypes = { + /** + * Children components of the Button + * it can be a text node, an image, or an icon + * or an Array with a combination of them + */ + children: PropTypes.any, + /** + * Styles to be applied to the Button + */ + style: ViewPropTypes.style +}; + +export default Button; diff --git a/app/components/Button/index.test.js b/app/components/Button/index.test.js new file mode 100644 index 00000000000..8dd39f76cd0 --- /dev/null +++ b/app/components/Button/index.test.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Button from './'; + +describe('Button', () => { + it('should render correctly', () => { + const wrapper = shallow(