Browse Source

WIP: redesign

localNotifications
Overtorment 6 years ago
parent
commit
55f6ccde73
  1. 6
      App.test.js
  2. 564
      BlueComponents.js
  3. 8
      MainBottomTabs.js
  4. 4
      app.json
  5. 4
      class/abstract-wallet.js
  6. 44
      class/app-storage.js
  7. 7
      class/legacy-wallet.js
  8. 1
      events.js
  9. BIN
      img/btc-shape.png
  10. BIN
      img/icon.png
  11. BIN
      img/lnd-shape.png
  12. 9
      loc/en.js
  13. 18
      loc/index.js
  14. 5
      loc/ru.js
  15. 37
      package-lock.json
  16. 4
      package.json
  17. 24
      screen/about.js
  18. 12
      screen/plausibledeniability.js
  19. 4
      screen/receive.js
  20. 54
      screen/receive/details.js
  21. 27
      screen/selftest.js
  22. 12
      screen/send/create.js
  23. 6
      screen/send/details.js
  24. 41
      screen/settings.js
  25. 12
      screen/wallets/add.js
  26. 12
      screen/wallets/details.js
  27. 338
      screen/wallets/list.js

6
App.test.js

@ -2,7 +2,6 @@
import React from 'react';
import { LegacyWallet, SegwitP2SHWallet, AppStorage } from './class';
import renderer from 'react-test-renderer';
import App from './App';
import Settings from './screen/settings';
import Selftest from './screen/selftest';
import { BlueHeader } from './BlueComponents';
@ -39,11 +38,6 @@ describe('unit - LegacyWallet', function() {
});
});
it('App does not crash', () => {
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});
it('BlueHeader works', () => {
const rendered = renderer.create(<BlueHeader />).toJSON();
expect(rendered).toBeTruthy();

564
BlueComponents.js

@ -1,7 +1,10 @@
/** @type {AppStorage} */
import React, { Component } from 'react';
import { SafeAreaView } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { LinearGradient } from 'expo';
import {
Icon,
Button,
FormLabel,
FormInput,
@ -11,7 +14,16 @@ import {
List,
ListItem,
} from 'react-native-elements';
import { ActivityIndicator, ListView, View, Dimensions } from 'react-native';
import {
TouchableOpacity,
ActivityIndicator,
View,
StyleSheet,
Dimensions,
Image,
} from 'react-native';
import Carousel from 'react-native-snap-carousel';
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
const { height, width } = Dimensions.get('window');
@ -61,8 +73,15 @@ export class BlueCard extends Component {
<Card
{...this.props}
titleStyle={{ color: BlueApp.settings.foregroundColor }}
containerStyle={{ backgroundColor: BlueApp.settings.brandingColor }}
wrapperStyle={{ backgroundColor: BlueApp.settings.brandingColor }}
containerStyle={{
backgroundColor: 'transparent',
borderColor: 'transparent',
}}
dividerStyle={{
backgroundColor: 'transparent',
borderColor: 'transparent',
}}
wrapperStyle={{ backgroundColor: 'transparent' }}
/>
);
}
@ -85,12 +104,16 @@ export class BlueListItem extends Component {
<ListItem
{...this.props}
containerStyle={{
backgroundColor: BlueApp.settings.brandingColor,
borderBottomColor: BlueApp.settings.foregroundColor,
borderBottomWidth: 0.5,
backgroundColor: 'transparent',
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
titleStyle={{ color: BlueApp.settings.foregroundColor, fontSize: 18 }}
subtitleStyle={{ color: BlueApp.settings.foregroundColor }}
titleStyle={{
color: BlueApp.settings.foregroundColor,
fontSize: 16,
fontWeight: '500',
}}
subtitleStyle={{ color: '#9aa0aa' }}
/>
);
}
@ -145,7 +168,12 @@ export class BlueHeader extends Component {
return (
<Header
{...this.props}
backgroundColor={BlueApp.settings.brandingColor}
backgroundColor="transparent"
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
statusBarProps={{ barStyle: 'light-content' }}
/>
);
}
@ -173,56 +201,528 @@ export class BlueSpacing40 extends Component {
}
export class BlueSpacing20 extends Component {
render() {
return <View {...this.props} style={{ height: 20, opacity: 0 }} />;
}
}
export class BlueList extends Component {
render() {
return (
<View
<List
{...this.props}
style={{ height: 20, backgroundColor: BlueApp.settings.brandingColor }}
containerStyle={{
backgroundColor: BlueApp.settings.brandingColor,
borderTopColor: 'transparent',
borderTopWidth: 0,
}}
/>
);
}
}
export class BlueListView extends Component {
export class BlueLoading extends Component {
render() {
return <ListView {...this.props} />;
return (
<SafeBlueArea>
<View style={{ flex: 1, paddingTop: 200 }}>
<ActivityIndicator />
</View>
</SafeBlueArea>
);
}
}
export class BlueList extends Component {
const stylesBlueIcon = StyleSheet.create({
container: {
flex: 1,
},
containerRefresh: {
flex: 1,
position: 'absolute',
right: 10,
},
box1: {
position: 'relative',
top: 15,
},
boxIncomming: {
position: 'relative',
},
ball: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#ccddf9',
},
ballIncomming: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#d2f8d6',
transform: [{ rotate: '-45deg' }],
},
ballReceive: {
width: 30,
height: 30,
borderBottomLeftRadius: 15,
backgroundColor: '#d2f8d6',
transform: [{ rotate: '-45deg' }],
},
ballOutgoing: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#f8d2d2',
transform: [{ rotate: '225deg' }],
},
ballTransparrent: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'transparent',
},
ballDimmed: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'gray',
},
});
export class BluePlusIcon extends Component {
render() {
return (
<List
{...this.props}
containerStyle={{
backgroundColor: BlueApp.settings.brandingColor,
borderTopColor: BlueApp.settings.foregroundColor,
borderTopWidth: 1,
}}
/>
<View {...this.props} style={stylesBlueIcon.container}>
<View style={stylesBlueIcon.box1}>
<View style={stylesBlueIcon.ball}>
<Ionicons
{...this.props}
name={'ios-add'}
size={26}
style={{
color: BlueApp.settings.foregroundColor,
backgroundColor: 'transparent',
left: 8,
top: 1,
}}
/>
</View>
</View>
</View>
);
}
}
export class BlueView extends Component {
export class BlueRefreshIcon extends Component {
render() {
return (
<View
{...this.props}
containerStyle={{ backgroundColor: BlueApp.settings.brandingColor }}
/>
<TouchableOpacity {...this.props} style={stylesBlueIcon.containerRefresh}>
<View style={stylesBlueIcon.box1}>
<View style={stylesBlueIcon.ball}>
<Ionicons
{...this.props}
name={'ios-refresh'}
size={26}
style={{
color: BlueApp.settings.foregroundColor,
backgroundColor: 'transparent',
left: 8,
top: 2,
}}
/>
</View>
</View>
</TouchableOpacity>
);
}
}
export class BlueLoading extends Component {
export class BlueTransactionIncommingIcon extends Component {
render() {
return (
<SafeBlueArea>
<View style={{ flex: 1, paddingTop: 200 }}>
<ActivityIndicator />
<View {...this.props} style={stylesBlueIcon.container}>
<View style={stylesBlueIcon.boxIncomming}>
<View style={stylesBlueIcon.ballIncomming}>
<Icon
{...this.props}
name="arrow-down"
size={16}
type="font-awesome"
color="#37c0a1"
iconStyle={{ left: 0, top: 8 }}
/>
</View>
</View>
</SafeBlueArea>
</View>
);
}
}
export class BlueTransactionPendingIcon extends Component {
render() {
return (
<View {...this.props} style={stylesBlueIcon.container}>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ball}>
<Icon
{...this.props}
name="ellipsis-h"
size={16}
type="font-awesome"
color={BlueApp.settings.foregroundColor}
iconStyle={{ left: 0, top: 6 }}
/>
</View>
</View>
</View>
);
}
}
export class BlueTransactionOutgoingIcon extends Component {
render() {
return (
<View {...this.props} style={stylesBlueIcon.container}>
<View style={stylesBlueIcon.boxIncomming}>
<View style={stylesBlueIcon.ballOutgoing}>
<Icon
{...this.props}
name="arrow-down"
size={16}
type="font-awesome"
color="#d0021b"
iconStyle={{ left: 0, top: 8 }}
/>
</View>
</View>
</View>
);
}
}
export class BlueReceiveButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props}>
<View
{...this.props}
style={{ flex: 1, position: 'absolute', bottom: 30, left: 80 }}
>
<View
style={{
flex: 1,
flexDirection: 'row',
width: 100,
height: 40,
position: 'relative',
backgroundColor: '#ccddf9',
borderBottomLeftRadius: 15,
borderTopLeftRadius: 15,
}}
>
<View
style={{
width: 30,
height: 30,
borderBottomLeftRadius: 15,
backgroundColor: 'transparent',
transform: [{ rotate: '-45deg' }],
}}
>
<Icon
{...this.props}
name="arrow-down"
size={16}
type="font-awesome"
color="#2f5fb3"
iconStyle={{ left: 0, top: 15 }}
/>
</View>
<Text
style={{
color: BlueApp.settings.foregroundColor,
fontSize: 16,
fontWeight: '500',
left: 5,
top: 12,
backgroundColor: 'transparent',
position: 'relative',
}}
>
{loc.receive.list.header}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export class BlueSendButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props}>
<View
{...this.props}
style={{ flex: 1, position: 'absolute', bottom: 30, right: 85 }}
>
<View
style={{
flex: 1,
flexDirection: 'row',
width: 110,
height: 40,
position: 'relative',
backgroundColor: '#ccddf9',
borderBottomRightRadius: 15,
borderTopRightRadius: 15,
}}
>
<View
style={{
width: 30,
height: 30,
left: 5,
borderBottomLeftRadius: 15,
backgroundColor: 'transparent',
transform: [{ rotate: '225deg' }],
}}
>
<Icon
{...this.props}
name="arrow-down"
size={16}
type="font-awesome"
color="#2f5fb3"
iconStyle={{ left: 0, top: 0 }}
/>
</View>
<Text
style={{
color: BlueApp.settings.foregroundColor,
fontSize: 16,
fontWeight: '500',
left: 5,
top: 12,
backgroundColor: 'transparent',
position: 'relative',
}}
>
{loc.send.list.header}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export class BluePlusIconDimmed extends Component {
render() {
return (
<View {...this.props} style={stylesBlueIcon.container}>
<View style={stylesBlueIcon.box1}>
<View style={stylesBlueIcon.ballDimmed}>
<Ionicons
{...this.props}
name={'ios-add'}
size={26}
style={{
color: 'white',
backgroundColor: 'transparent',
left: 8,
top: 1,
}}
/>
</View>
</View>
</View>
);
}
}
export class NewWalletPannel extends Component {
constructor(props) {
super(props);
// WalletsCarousel.handleClick = props.handleClick // because cant access `this` from _renderItem
// eslint-disable-next-line
this.handleClick = props.onPress;
}
render() {
return (
<TouchableOpacity
{...this.props}
activeOpacity={1}
style={{ paddingRight: 10, left: -20, paddingTop: 20 }}
onPress={() => {
if (this.handleClick) {
this.handleClick();
}
}}
>
<LinearGradient
colors={['#eef0f4', '#eef0f4']}
style={{
padding: 15,
borderRadius: 10,
height: 145,
justifyContent: 'center',
alignItems: 'center',
}}
>
<BluePlusIconDimmed />
<Text
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 20,
color: '#9aa0aa',
}}
>
{loc.wallets.list.create_a_wallet}
</Text>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: '#9aa0aa',
}}
>
{loc.wallets.list.create_a_wallet1}
</Text>
<Text
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: '#9aa0aa',
}}
>
{loc.wallets.list.create_a_wallet2}
</Text>
</LinearGradient>
</TouchableOpacity>
);
}
}
let sliderWidth = width * 1;
let itemWidth = width * 0.82;
let sliderHeight = 175;
export class WalletsCarousel extends Component {
constructor(props) {
super(props);
// eslint-disable-next-line
WalletsCarousel.handleClick = props.handleClick; // because cant access `this` from _renderItem
// eslint-disable-next-line
this.onSnapToItem = props.onSnapToItem;
}
_renderItem({ item, index }) {
if (!item) {
return (
<NewWalletPannel
onPress={() => {
if (WalletsCarousel.handleClick) {
WalletsCarousel.handleClick(index);
}
}}
/>
);
}
return (
<TouchableOpacity
activeOpacity={1}
style={{ paddingRight: 10, left: -20, paddingTop: 20 }}
onPress={() => {
if (WalletsCarousel.handleClick) {
WalletsCarousel.handleClick(index);
}
}}
>
<LinearGradient
colors={['#65ceef', '#68bbe1']}
style={{ padding: 15, borderRadius: 10 }}
>
<Image
source={require('./img/btc-shape.png')}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
}}
>
{item.getLabel()}
</Text>
<Text
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
}}
>
{item.getBalance()} BTC
</Text>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: '#fff',
}}
>
latest transaction
</Text>
<Text
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 16,
color: '#fff',
}}
>
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
</Text>
</LinearGradient>
</TouchableOpacity>
);
}
render() {
return (
<View style={{ height: sliderHeight }}>
<Carousel
{...this.props}
ref={c => {
WalletsCarousel.carousel = c;
}}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
itemWidth={itemWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={0.7}
onSnapToItem={index => {
if (this.onSnapToItem) {
this.onSnapToItem(index);
}
console.log('snapped to card #', index);
}}
/>
</View>
);
}
}

8
MainBottomTabs.js

@ -5,6 +5,7 @@ import wallets from './screen/wallets';
import send from './screen/send';
import settins from './screen/settings';
import receive from './screen/receive';
import details from './screen/receive/details';
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
@ -31,10 +32,17 @@ const Tabs = TabNavigator(
screen: settins,
path: 'settings',
},
//
ReceiveDetails: {
screen: details,
},
},
{
tabBarPosition: 'bottom',
animationEnabled: true,
tabBarVisible: false,
tabBarOptions: {
showLabel: false,
activeTintColor: BlueApp.settings.foregroundColor,

4
app.json

@ -1,13 +1,13 @@
{
"expo": {
"sdkVersion": "23.0.0",
"version": "1.4.2",
"version": "2.0.0",
"privacy": "public",
"platforms": [
"ios"
],
"ios": {
"buildNumber": "39",
"buildNumber": "40",
"supportsTablet": false,
"isRemoteJSEnabled": false,
"bundleIdentifier": "io.bluewallet.bluewallet",

4
class/abstract-wallet.js

@ -43,6 +43,10 @@ export class AbstractWallet {
return this;
}
getLatestTransactionTime() {
return 0;
}
static fromJson(obj) {
let obj2 = JSON.parse(obj);
let temp = new this();

44
class/app-storage.js

@ -12,14 +12,15 @@ export class AppStorage {
this.tx_metadata = {};
this.cachedPassword = false;
this.settings = {
brandingColor: '#008dc2',
foregroundColor: '#ffffff',
buttonBackground: '#008dc2',
buttonTextColor: '#ffffff',
brandingColor: '#ffffff',
foregroundColor: '#0c2550',
buttonBackground: '#ffffff',
buttonTextColor: '#0c2550',
};
}
async storageIsEncrypted() {
// await AsyncStorage.clear();
let data;
try {
data = await AsyncStorage.getItem(AppStorage.FLAG_ENCRYPTED);
@ -224,12 +225,24 @@ export class AppStorage {
* Fetches from remote endpoint all transactions for each wallet.
* Returns void.
* To access transactions - get them from each respective wallet.
* If index is present then fetch only from this specific wallet
*
* @param index {Integer} Index of the wallet in this.wallets array,
* blank to fetch from all wallets
* @return {Promise.<void>}
*/
async fetchWalletTransactions() {
for (let wallet of this.wallets) {
await wallet.fetchTransactions();
async fetchWalletTransactions(index) {
if (index || index === 0) {
let c = 0;
for (let wallet of this.wallets) {
if (c++ === index) {
await wallet.fetchTransactions();
}
}
} else {
for (let wallet of this.wallets) {
await wallet.fetchTransactions();
}
}
}
@ -242,11 +255,24 @@ export class AppStorage {
}
/**
* Getter for all transactions in all wallets
* Getter for all transactions in all wallets.
* But if index is provided - only for wallet with corresponding index
*
* @param index {Integer} Wallet index in this.wallets. Empty for all wallets.
* @return {Array}
*/
getTransactions() {
getTransactions(index) {
if (index || index === 0) {
let txs = [];
let c = 0;
for (let wallet of this.wallets) {
if (c++ === index) {
txs = txs.concat(wallet.transactions);
}
}
return txs;
}
let txs = [];
for (let wallet of this.wallets) {
txs = txs.concat(wallet.transactions);

7
class/legacy-wallet.js

@ -349,6 +349,13 @@ export class LegacyWallet extends AbstractWallet {
);
}
getLatestTransactionTime() {
for (let tx of this.transactions) {
return tx.received;
}
return 0;
}
getRandomBlockcypherToken() {
return (array => {
for (let i = array.length - 1; i > 0; i--) {

1
events.js

@ -29,6 +29,7 @@ EV.enum = {
TRANSACTIONS_COUNT_CHANGED: 'TRANSACTIONS_COUNT_CHANGED',
CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS:
'CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS',
RECEIVE_ADDRESS_CHANGED: 'RECEIVE_ADDRESS_CHANGED',
};
module.exports = EV;

BIN
img/btc-shape.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
img/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 252 KiB

BIN
img/lnd-shape.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

9
loc/en.js

@ -9,11 +9,14 @@ module.exports = {
list: {
tabBarLabel: 'Wallets',
app_name: 'Blue Wallet',
title: 'My Bitcoin Wallets',
title: 'wallets',
header:
'A wallet represents a pair of a secret (private key) and an address' +
'you can share to receive coins.',
add: 'Add Wallet',
create_a_wallet: 'Create a wallet',
create_a_wallet1: "It's free and you can create",
create_a_wallet2: 'as many as you like',
},
add: {
title: 'Add Wallet',
@ -55,7 +58,7 @@ module.exports = {
transactions: {
list: {
tabBarLabel: 'Transactions',
title: 'My Transactions',
title: 'transactions',
description: 'A list of ingoing or outgoing transactions of your wallets',
conf: 'conf',
},
@ -102,7 +105,7 @@ module.exports = {
receive: {
list: {
tabBarLabel: 'Receive',
header: 'Receive',
header: 'receive',
},
details: {
title: 'Share this address with payer',

18
loc/index.js

@ -45,4 +45,22 @@ strings = new LocalizedStrings({
strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang);
strings.transactionTimeToReadable = function(time) {
if (time === 0) {
return 'never';
}
let ago = (Date.now() - Date.parse(time)) / 1000; // seconds
if (ago / (3600 * 24) >= 30) {
ago = Math.round(ago / (3600 * 24 * 30));
return ago + ' months ago';
} else if (ago / (3600 * 24) >= 1) {
ago = Math.round(ago / (3600 * 24));
return ago + ' days ago';
} else {
ago = Math.round(ago / 3600);
return ago + ' hours ago';
}
};
module.exports = strings;

5
loc/ru.js

@ -9,10 +9,13 @@ module.exports = {
list: {
tabBarLabel: 'Кошельки',
app_name: 'BlueWallet',
title: 'Мои Биткоин Кошельки',
title: 'Мои Кошельки',
header:
'Кошелек это секретный (приватный) ключ, и соответствующий ему адрес на который можно получать биткоины',
add: 'Добавить Кошелек',
create_a_wallet: 'Создать кошелек',
create_a_wallet1: 'Это бесплатно и вы можете создать',
create_a_wallet2: 'неограниченное количество',
},
add: {
title: 'Добавить Кошелек',

37
package-lock.json

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "1.3.0",
"version": "1.4.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1990,6 +1990,18 @@
"qs": "6.5.1"
}
},
"bip39": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/bip39/-/bip39-2.5.0.tgz",
"integrity": "sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA==",
"requires": {
"create-hash": "1.1.3",
"pbkdf2": "3.0.14",
"randombytes": "2.0.6",
"safe-buffer": "5.1.1",
"unorm": "1.4.1"
}
},
"bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
@ -10033,6 +10045,15 @@
"prop-types": "15.6.1"
}
},
"react-addons-shallow-compare": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.2.tgz",
"integrity": "sha1-GYoAuR/DdiPbZKKP0XtZa6NicC8=",
"requires": {
"fbjs": "0.8.16",
"object-assign": "4.1.1"
}
},
"react-clone-referenced-element": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-clone-referenced-element/-/react-clone-referenced-element-1.0.1.tgz",
@ -10396,6 +10417,15 @@
}
}
},
"react-native-snap-carousel": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/react-native-snap-carousel/-/react-native-snap-carousel-3.7.2.tgz",
"integrity": "sha512-/XHc6IYRgTcwtxkgJwYRLJHdVC7vy0Mu/3gJ6ydVFGH3AS3M2Yyr4OhfNpetQSMdZ8IND92ViKezqlwQVzRwqQ==",
"requires": {
"prop-types": "15.6.1",
"react-addons-shallow-compare": "15.6.2"
}
},
"react-native-svg": {
"version": "https://github.com/expo/react-native-svg/archive/5.5.1-exp.1.tar.gz",
"integrity": "sha1-DG43Pb5jz8vdRl9bKWXr4BHIli8=",
@ -13260,6 +13290,11 @@
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=",
"dev": true
},
"unorm": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz",
"integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

4
package.json

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "1.4.2",
"version": "2.0.0",
"devDependencies": {
"babel-eslint": "^8.2.2",
"eslint": "^4.19.0",
@ -40,6 +40,7 @@
"asyncstorage-down": "^3.1.1",
"bignumber.js": "^5.0.0",
"bip21": "^2.0.1",
"bip39": "^2.5.0",
"bitcoinjs-lib": "^3.3.2",
"buffer": "^4.9.1",
"crypto-js": "^3.1.9-1",
@ -60,6 +61,7 @@
"react-native-elements": "^0.18.5",
"react-native-level-fs": "^3.0.0",
"react-native-qrcode": "^0.2.6",
"react-native-snap-carousel": "^3.7.2",
"react-navigation": "^1.0.0-beta.23",
"readable-stream": "^1.1.14",
"request-promise-native": "^1.0.5",

24
screen/about.js

@ -70,7 +70,11 @@ export default class About extends Component {
</BlueText>
<BlueButton
icon={{ name: 'mark-github', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'mark-github',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://github.com/Overtorment/BlueWallet');
}}
@ -78,7 +82,11 @@ export default class About extends Component {
/>
<BlueButton
icon={{ name: 'twitter', type: 'font-awesome', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'twitter',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL('https://twitter.com/bluewalletio');
}}
@ -86,7 +94,11 @@ export default class About extends Component {
/>
<BlueButton
icon={{ name: 'thumbsup', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'thumbsup',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => {
Linking.openURL(
'https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8',
@ -96,7 +108,11 @@ export default class About extends Component {
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'arrow-left',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
title="Go Back"
onPress={() => {
this.props.navigation.goBack();

12
screen/plausibledeniability.js

@ -66,7 +66,11 @@ export default class PlausibleDeniability extends Component {
<BlueText>{loc.plausibledeniability.help2}</BlueText>
<BlueButton
icon={{ name: 'shield', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'shield',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
title={loc.plausibledeniability.create_fake_storage}
onPress={async () => {
let p1 = await prompt(
@ -97,7 +101,11 @@ export default class PlausibleDeniability extends Component {
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'arrow-left',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
title={loc.plausibledeniability.go_back}
onPress={() => {
this.props.navigation.goBack();

4
screen/receive.js

@ -1,16 +1,12 @@
import { StackNavigator } from 'react-navigation';
import list from './receive/list';
import details from './receive/details';
const ReceiveNavigator = StackNavigator(
{
SendList: {
screen: list,
},
ReceiveDetails: {
screen: details,
},
},
{
headerMode: 'none',

54
screen/receive/details.js

@ -1,18 +1,20 @@
import React, { Component } from 'react';
import { Dimensions } from 'react-native';
import { Dimensions, Text, TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
import Ionicons from 'react-native-vector-icons/Ionicons';
import QRCode from 'react-native-qrcode';
import {
BlueLoading,
BlueSpacing40,
BlueHeader,
BlueFormInputAddress,
SafeBlueArea,
BlueCard,
BlueSpacing,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let EV = require('../../events');
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
let isIpad;
@ -24,6 +26,7 @@ if (aspectRatio > 1.6) {
export default class ReceiveDetails extends Component {
static navigationOptions = {
tabBarVisible: false,
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-cash' : 'ios-cash-outline'}
@ -41,6 +44,15 @@ export default class ReceiveDetails extends Component {
address: address,
};
console.log(JSON.stringify(address));
EV(EV.enum.RECEIVE_ADDRESS_CHANGED, this.refreshFunction.bind(this));
}
refreshFunction(newAddress) {
console.log('newAddress =', newAddress);
this.setState({
address: newAddress,
});
}
async componentDidMount() {
@ -51,31 +63,45 @@ export default class ReceiveDetails extends Component {
}
render() {
console.log('render() receive/details, address=', this.state.address);
if (this.state.isLoading) {
return <BlueLoading />;
}
return (
<SafeBlueArea style={{ flex: 1 }}>
{(() => {
if (isIpad) {
return <BlueSpacing40 />;
} else {
return <BlueSpacing />;
<BlueHeader
leftComponent={
<Text
style={{
fontWeight: 'bold',
fontSize: 34,
color: BlueApp.settings.foregroundColor,
}}
>
{loc.receive.list.header}
</Text>
}
rightComponent={
<TouchableOpacity onPress={() => this.props.navigation.goBack()}>
<Icon
name="times"
size={16}
type="font-awesome"
color={BlueApp.settings.foregroundColor}
/>
</TouchableOpacity>
}
})()}
/>
<BlueCard
title={loc.receive.details.title}
style={{ alignItems: 'center', flex: 1 }}
>
<BlueFormInputAddress editable value={this.state.address} />
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<QRCode
value={this.state.address}
size={(isIpad && 250) || 312}
bgColor={BlueApp.settings.foregroundColor}
fgColor={BlueApp.settings.brandingColor}
/>
<BlueFormInputAddress editable value={this.state.address} />
</BlueCard>
</SafeBlueArea>
);

27
screen/selftest.js

@ -219,6 +219,33 @@ export default class Selftest extends Component {
isOk = false;
}
//
let bip39 = require('bip39');
let mnemonic =
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/49'/0'/0'/0/0";
let child = root.derivePath(path);
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
let address = bitcoin.address.fromOutputScript(
outputScript,
bitcoin.networks.bitcoin,
);
if (address !== '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK') {
errorMessage += 'bip49 is not ok; ';
isOk = false;
}
//
this.setState({
isLoading: false,
isOk,

12
screen/send/create.js

@ -198,13 +198,21 @@ export default class SendCreate extends Component {
</BlueCard>
<BlueButton
icon={{ name: 'megaphone', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'megaphone',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => this.broadcast()}
title={loc.send.create.broadcast}
/>
<BlueButton
icon={{ name: 'arrow-left', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'arrow-left',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() => this.props.navigation.goBack()}
title={loc.send.create.go_back}
/>

6
screen/send/details.js

@ -245,7 +245,11 @@ export default class SendDetails extends Component {
</View>
<View style={{ flex: 0.33 }}>
<BlueButton
icon={{ name: 'qrcode', type: 'font-awesome', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'qrcode',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
style={{}}
title={loc.send.details.scan}
onPress={() => this.props.navigation.navigate('ScanQrAddress')}

41
screen/settings.js

@ -1,11 +1,10 @@
/* global alert */
import React, { Component } from 'react';
import { ScrollView, View, Picker } from 'react-native';
import { ScrollView, TouchableOpacity, Text, View, Picker } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Icon, FormValidationMessage } from 'react-native-elements';
import {
BlueLoading,
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
@ -21,6 +20,7 @@ let loc = require('../loc');
export default class Settings extends Component {
static navigationOptions = {
tabBarLabel: loc.settings.tabBarLabel,
tabBarVisible: false,
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings-outline'}
@ -53,23 +53,31 @@ export default class Settings extends Component {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueHeader
backgroundColor={BlueApp.settings.brandingColor}
leftComponent={
<Icon
name="menu"
color={BlueApp.settings.foregroundColor}
onPress={() => this.props.navigation.navigate('DrawerToggle')}
/>
<Text
style={{
fontWeight: 'bold',
fontSize: 34,
color: BlueApp.settings.foregroundColor,
}}
>
{loc.settings.header}
</Text>
}
rightComponent={
<TouchableOpacity onPress={() => this.props.navigation.goBack()}>
<Icon
name="times"
size={16}
type="font-awesome"
color={BlueApp.settings.foregroundColor}
/>
</TouchableOpacity>
}
centerComponent={{
text: loc.settings.header,
style: { color: BlueApp.settings.foregroundColor, fontSize: 23 },
}}
/>
<BlueCard>
<ScrollView maxHeight={450}>
<BlueSpacing20 />
{(() => {
if (this.state.storageIsEncrypted) {
return (
@ -90,7 +98,11 @@ export default class Settings extends Component {
{loc.settings.storage_not_encrypted}
</FormValidationMessage>
<BlueButton
icon={{ name: 'shield', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'shield',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
this.setState({ isLoading: true });
let p1 = await prompt(
@ -173,5 +185,6 @@ export default class Settings extends Component {
Settings.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};

12
screen/wallets/add.js

@ -58,7 +58,11 @@ export default class WalletsAdd extends Component {
<BlueButton
large
icon={{ name: 'qrcode', type: 'font-awesome', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'qrcode',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={loc.wallets.add.scan}
onPress={() => {
this.props.navigation.navigate('ScanQrWif');
@ -67,7 +71,11 @@ export default class WalletsAdd extends Component {
<BlueButton
large
icon={{ name: 'bitcoin', type: 'font-awesome', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'bitcoin',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={loc.wallets.add.create}
onPress={() => {
this.props.navigation.goBack();

12
screen/wallets/details.js

@ -124,7 +124,11 @@ export default class WalletDetails extends Component {
<View style={{ flex: 0, flexDirection: 'row' }}>
<View style={{ flex: 0.5 }}>
<BlueButton
icon={{ name: 'stop', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'stop',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
BlueApp.deleteWallet(this.state.wallet);
await BlueApp.saveToDisk();
@ -150,7 +154,11 @@ export default class WalletDetails extends Component {
return (
<View>
<BlueButton
icon={{ name: 'stop', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
icon={{
name: 'stop',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
this.setState({ confirmDelete: true });
}}

338
screen/wallets/list.js

@ -1,15 +1,26 @@
import React, { Component } from 'react';
import { ListView, Dimensions } from 'react-native';
import {
View,
TouchableOpacity,
Dimensions,
Text,
ListView,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Icon } from 'react-native-elements';
import {
BlueLoading,
BlueList,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueListItem,
BlueHeader,
WalletsCarousel,
BlueTransactionIncommingIcon,
BlueTransactionOutgoingIcon,
BlueTransactionPendingIcon,
BlueSendButtonIcon,
BlueReceiveButtonIcon,
BlueRefreshIcon,
BlueList,
BlueListItem,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
let EV = require('../../events');
@ -17,19 +28,13 @@ let EV = require('../../events');
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
} else {
isIpad = true;
}
let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
export default class WalletsList extends Component {
static navigationOptions = {
tabBarLabel: loc.wallets.list.tabBarLabel,
tabBarVisible: false,
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-briefcase' : 'ios-briefcase-outline'}
@ -51,8 +56,45 @@ export default class WalletsList extends Component {
this.refreshFunction();
} // end of componendDidMount
refreshFunction() {
refreshTransactions() {
this.setState(
{
isTransactionsLoading: true,
},
async function() {
let that = this;
setTimeout(async function() {
// more responsive
let noErr = true;
try {
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0);
} catch (err) {
noErr = false;
console.warn(err);
}
if (noErr) await BlueApp.saveToDisk(); // caching
that.refreshFunction();
}, 1);
},
);
}
refreshFunction() {
setTimeout(() => {
this.setState({
isLoading: false,
isTransactionsLoading: false,
showReceiveButton: true,
showSendButton: true,
final_balance: BlueApp.getBalance(),
dataSource: ds.cloneWithRows(
BlueApp.getTransactions(this.lastSnappedTo || 0),
),
});
}, 1);
/* this.setState(
{
isLoading: true,
},
@ -60,11 +102,67 @@ export default class WalletsList extends Component {
setTimeout(() => {
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(BlueApp.getWallets()),
final_balance: BlueApp.getBalance(),
dataSource: ds.cloneWithRows(BlueApp.getTransactions()),
});
}, 1);
},
);
); */
// this.forceUpdate();
/* this.setState(
{
isLoading: true,
},
() => {
setTimeout(() => {
this.setState({
isLoading: false,
});
}, 1);
},
); */
}
txMemo(hash) {
if (BlueApp.tx_metadata[hash] && BlueApp.tx_metadata[hash]['memo']) {
return ' | ' + BlueApp.tx_metadata[hash]['memo'];
}
return '';
}
handleClick(index) {
console.log('cick', index);
let wallet = BlueApp.wallets[index];
if (wallet) {
this.props.navigation.navigate('WalletDetails', {
address: wallet.getAddress(),
});
} else {
// if its out of index - this must be last card with incentive to create wallet
this.props.navigation.navigate('AddWallet');
}
}
onSnapToItem(index) {
console.log('onSnapToItem', index);
this.lastSnappedTo = index;
this.setState({
isLoading: false,
showReceiveButton: false,
showSendButton: false,
final_balance: BlueApp.getBalance(),
dataSource: ds.cloneWithRows(BlueApp.getTransactions(index)),
});
if (index < BlueApp.getWallets().length) {
// do not show for last card
setTimeout(
() => this.setState({ showReceiveButton: true, showSendButton: true }),
50,
); // just to animate it, no real function
}
}
render() {
@ -77,53 +175,177 @@ export default class WalletsList extends Component {
return (
<SafeBlueArea>
<BlueHeader
centerComponent={{
text: loc.wallets.list.app_name,
style: { color: BlueApp.settings.foregroundColor, fontSize: 23 },
leftComponent={
<Text
style={{
fontWeight: 'bold',
fontSize: 34,
color: BlueApp.settings.foregroundColor,
}}
>
{loc.wallets.list.title}
</Text>
}
rightComponent={
<TouchableOpacity onPress={() => navigate('Settings')}>
<Icon
name="ellipsis-h"
size={16}
type="font-awesome"
color={BlueApp.settings.foregroundColor}
/>
</TouchableOpacity>
}
/>
<WalletsCarousel
data={BlueApp.getWallets().concat(false)}
handleClick={index => {
this.handleClick(index);
}}
onSnapToItem={index => {
this.onSnapToItem(index);
}}
/>
<BlueCard title={loc.wallets.list.title}>
<BlueText style={{ marginBottom: 10 }}>
{loc.wallets.list.header}
</BlueText>
<BlueList>
<ListView
enableEmptySections
maxHeight={(isIpad && 60) || height - 390}
dataSource={this.state.dataSource}
renderRow={rowData => {
return (
<BlueListItem
onPress={() => {
navigate('WalletDetails', {
address: rowData.getAddress(),
});
}}
leftIcon={{
name: 'bitcoin',
type: 'font-awesome',
{(() => {
if (this.state.isTransactionsLoading) {
return <BlueLoading />;
} else {
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1, flexDirection: 'row' }}>
<Text
style={{
paddingLeft: 15,
paddingTop: 15,
fontWeight: 'bold',
fontSize: 24,
color: BlueApp.settings.foregroundColor,
}}
title={
rowData.getLabel() + ' | ' + rowData.getBalance() + ' BTC'
}
subtitle={rowData.getShortAddress()}
hideChevron={false}
>
{loc.transactions.list.title}
</Text>
<BlueRefreshIcon onPress={() => this.refreshTransactions()} />
</View>
<BlueList>
<ListView
maxHeight={height - 330 + 10}
width={width - 5}
left={5}
top={-10}
enableEmptySections
dataSource={this.state.dataSource}
renderRow={rowData => {
return (
<BlueListItem
avatar={(() => {
if (!rowData.confirmations) {
return (
<View style={{ width: 25 }}>
<BlueTransactionPendingIcon />
</View>
);
} else if (rowData.value < 0) {
return (
<View style={{ width: 25 }}>
<BlueTransactionOutgoingIcon />
</View>
);
} else {
return (
<View style={{ width: 25 }}>
<BlueTransactionIncommingIcon />
</View>
);
}
})()}
title={loc.transactionTimeToReadable(
rowData.received,
)}
subtitle={
this.txMemo(rowData.hash) +
(rowData.confirmations < 200
? loc.transactions.list.conf +
': ' +
rowData.confirmations
: '')
}
onPress={() => {
navigate('TransactionDetails', {
hash: rowData.hash,
});
}}
badge={{
value: 3,
textStyle: { color: 'orange' },
containerStyle: { marginTop: 0 },
}}
chevron={false}
chevronColor="transparent"
rightTitle={rowData.value / 100000000 + ''}
rightTitleStyle={{
position: 'relative',
right: -30,
top: -7,
fontWeight: '600',
fontSize: 16,
color:
rowData.value / 100000000 < 0
? BlueApp.settings.foregroundColor
: '#37c0a1',
}}
/>
);
}}
/>
);
}}
/>
</BlueList>
</BlueCard>
<BlueButton
icon={{ name: 'plus-small', type: 'octicon', color: BlueApp.settings.buttonTextColor }}
onPress={() => {
navigate('AddWallet');
}}
title={loc.wallets.list.add}
/>
</BlueList>
</View>
);
}
})()}
{(() => {
if (this.state.showReceiveButton) {
return (
<BlueReceiveButtonIcon
onPress={() => {
let walletIndex = this.lastSnappedTo || 0;
console.log('receiving on #', walletIndex);
let c = 0;
for (let w of BlueApp.getWallets()) {
if (c++ === walletIndex) {
console.log('found receiving address ', w.getAddress());
navigate('ReceiveDetails', { address: w.getAddress() });
EV(EV.enum.RECEIVE_ADDRESS_CHANGED, w.getAddress());
}
}
}}
/>
);
}
})()}
{(() => {
if (this.state.showReceiveButton) {
return (
<BlueSendButtonIcon
onPress={() => {
let walletIndex = this.lastSnappedTo || 0;
let c = 0;
for (let w of BlueApp.getWallets()) {
if (c++ === walletIndex) {
navigate('SendDetails', { fromAddress: w.getAddress() });
}
}
}}
/>
);
}
})()}
</SafeBlueArea>
);
}

Loading…
Cancel
Save