1,037 App.js
@@ -1,217 +1,1002 @@
import React from 'react';
import { StyleSheet, Text, View, TextInput, Button, TouchableOpacity, AsyncStorage, FlatList, Picker, Keyboard, Alert, Platform, RefreshControl, ScrollView } from 'react-native';
import autobind from 'autobind-decorator'
import { StyleSheet, Text, View, Image, TextInput, Button, Alert, FlatList, Fetch, TouchableOpacity, Picker } from 'react-native';
import { StackNavigator } from 'react-navigation';

class HomeScreen extends React.Component {
//style sheet resource info
const styles = StyleSheet.create({
cardTextInput: {
height: (Platform.OS == 'android' ? 40 : 20),
},
headerTouchableText: {
marginLeft: 20,
marginRight: 20,
color: '#2196F3',
},
cardText: {
margin: 5,
padding: 0,
fontSize: 15,
},
cardItem: {
margin: 10,
marginBottom: 0,
padding: 10,
backgroundColor: 'white',
borderRadius: 5,
},
homeNewAddressButton: {

},
homeAddressItemText: {

static navigationOptions = {
title: 'TF2Logs',
},
button: {
marginTop: 20,
margin: 10,
alignItems: 'center',
backgroundColor: '#2196F3',
borderRadius: 5
},
buttonText: {
padding: 5,
color: 'white',
fontSize: 20
},
avgHashBar: {
backgroundColor:'#2196F3',
borderRadius: 5,
},
nowHashBar: {
backgroundColor:'#7bdb78',
borderRadius: 5,
},
hashBarBG: {
backgroundColor:'white'
},

};
//older
myListItem: {
height: 50,
},
viewTitle: {
color: 'white',
fontWeight: 'bold',
fontSize: 50,
alignSelf: 'center',
},
balanceText: {
color: 'white',
fontWeight: 'bold',
fontSize: 30,
alignSelf: 'center',
},
addressInput: {
height: 40,
color: 'black',
backgroundColor: 'white',
alignSelf: 'center',
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

constructor(props) {

const HashrateUnits = {
ETH: 'Mh/s',
SIA: 'Mh/s,'
}

class HomeScreen extends React.Component {

constructor(props){
super(props);
this.state = {
player: '',
matches: [{id: '123', title: 'poopybutthole'}],
pairs: null,
};
}

@autobind
_onPressButton() {
// if(this.state.player){
this.fetchStats(this.state.player)
//}
fetchData(){
if(this.state.pairs){
for(var i = 0; i<this.state.pairs.length; i++){
//once per pair
try{
if(this.state.pairs[i].cryptocurrency == 'ETH'){
fetch('https://etherchain.org/api/account/' + this.state.pairs[i].address)
.then((response) => response.json())
.then((responseJson) => {
p = this.state.pairs;

this.addBalanceData(responseJson.data);
})
.catch((error) => {
console.error(error);
}
);
}
} catch(e){
console.log('Error parsing JSON for balance data');
}
}
}
}

/** Fetches the list of matches from logs.tf and stores it in the matches state
*/
fetchStats(player) {
return fetch('https://logs.tf/json_search?player=' + (player? player.toString():'76561198043059628'))
.then((response) => response.json())
.then((responseJson) => {
@autobind
addBalanceData(data){
balance = data[0] ? data[0].balance : null;
address = data[0] ? data[0].address : null;

p = this.state.pairs;
for(var i = 0; i<p.length; i++){
//once per pair
if(p[i].address == address){
//add address to the pair object in the pairs array
p[i].balance = balance;

}
}
this.setState({
pairs: p,
});
}


componentDidMount(){
//AsyncStorage.setItem('@DatStore:addresses','[]');
this.loadPairs().then(() =>{
//after pairs are loaded
this.fetchData()
});
}

loadPairs() {
return AsyncStorage.getItem('@DatStore:addresses')
.then((pp) => {
this.setState({
//add logs to the list
matches: responseJson.logs
}, function() {
//something to do w new states
});
})
.catch((error) => {
console.error(error);
pairs: JSON.parse(pp)
},(e)=>{
//do nothing about the errors here
});
});
}

@autobind
navToMatchScreen(matchInfo){
console.log('poopybutthole')
promptDeletePair(pair) {
Alert.alert(
'Delete Address?',
pair.name + '\n'
+ pair.cryptocurrency + '\n'
+ pair.address,
[
{text: 'Yes', onPress: () => this.deletePair(pair)},
{text: 'No'}
],
)
}

deletePair(pair) {
AsyncStorage.getItem('@DatStore:addresses')
.then(async (pairs) => {
//parse strigified object
data = JSON.parse(pairs);

testFunc = (p) => {
if(
p.address == pair.address &&
p.name == pair.name &&
p.cryptocurrency == pair.cryptocurrency){
return true;
}
return false;
};

//find the entry to delete
toDeleteIndex = data.findIndex(testFunc);

//remove the long pressed and confirmed entry
data.splice(toDeleteIndex,1);

try{
//update the stored pair list
AsyncStorage.setItem('@DatStore:addresses',JSON.stringify(data)).then((f) =>{
//reload the list of entries
this.loadPairs();
});

}catch(e){
console.log('Failed to delete pair');
}

});
}

@autobind
navToAddressStats(pair){
const { navigate } = this.props.navigation;
navigate('Match', matchInfo)
navigate('AddressStats',pair);
}

render() {


const { navigate } = this.props.navigation;
return (
<View style={{flex: 1, backgroundColor: 'powderblue'}}>
<View style={{flex:1, justifyContent: 'center'}}>
<View style={{flex:1, justifyContent: 'center'}}>
<TouchableOpacity onPress={() => navigate('NewAddress')} style={styles.homeNewAddressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>New Address</Text>
</View>
</TouchableOpacity>
</View>
<View style={{flex: 10,}}>
<AddressList style={{flex: 9,}}
data={this.state.pairs}
extraData={this.state}
onPressItem={this.navToAddressStats}
onLongPressItem={this.promptDeletePair}
onRefresh={this.fetchData}
/>
</View>
</View>
);
}
}

class NewAddressScreen extends React.Component {
static navigationOptions = {
title: 'New Address',
};

constructor(props){
super(props);
this.state = {newAddress: null,cryptocurrency: 'ETH',name:null};
}

componentDidMount(){
this.loadAddresses();
}

loadAddresses() {
return AsyncStorage.getItem('@DatStore:addresses')
.then((add) => {
this.setState({
addresses: add
},(e)=>{
//do nothing about the errors here
});
}).catch(error => {
this.setState({
addresses: []
});
});
}

saveNewAddressAndHome() {
const { navigate } = this.props.navigation

AsyncStorage.getItem('@DatStore:addresses')
.then(async (pairs) => {
//parse strigified object
data = JSON.parse(pairs);

//the new currency-address pair
//take everything after last slash
var parsed;
try{
var n = this.state.newAddress.lastIndexOf('/');
parsed = this.state.newAddress.substring(n + 1);
}catch(e){
console.log('Error parsing new address string')
console.log(e);
}
newPair = {cryptocurrency: this.state.cryptocurrency,address: (parsed == -1)? this.state.newAddress: parsed,name: this.state.name};

//if there is already a pair array
if(data){
//append the new pair to the end
try {
data.push(newPair);
}catch (e){
console.log('Failed to append new data pair');
}
}else{
//no data, make the array
data=[newPair];
}

try{
//update the stored pair list
await AsyncStorage.setItem('@DatStore:addresses',JSON.stringify(data));

//go back home
navigate('Home');

//dismiss the keyboard
try {
Keyboard.dismiss();
}catch(e){
console.log('Failed to dismiss Keyboard');
}

}catch(e){
console.log('Failed to store updated addresses or navigate to Home');
}

<View style={{marginTop: 60,margin: 40, padding: 10, flex: 1}}>
<TextInput
style={{height: 40, color: 'black'}}
textAlign="center"
placeholder="Enter Steam ID Number"
placeholderTextColor="black"
onChangeText={(text) => this.setState({'player':text})}
});
}


render() {
return (
<View style={{flex:1, }}>
<View style={styles.cardItem}>
<TextInput
style={styles.cardText, styles.cardTextInput}
placeholder={'Account Nickname'}
onChangeText={(name) => this.setState({'name': name})}
/>

<Button
onPress={this._onPressButton}
title="SEARCH"
<Picker
selectedValue={this.state.cryptocurrency}
onValueChange={(itemValue,itemIndex) => this.setState({'cryptocurrency': itemValue})}>
<Picker.Item label={'Ethereum'} value={'ETH'}/>
<Picker.Item label={'Siacoin'} value={'SIA'}/>
</Picker>
<TextInput
style={styles.cardText, styles.cardTextInput}
placeholder={'Address'}
onChangeText={(address) => this.setState({'newAddress': address})}
/>
</View>
<View style={styles.homeNewAddressButton}>
<TouchableOpacity onPress={() => this.saveNewAddressAndHome()} style={styles.homeNewAddressButton}>
<View style={styles.button}>
<Text style={styles.buttonText}>Add Address</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
}

class AddressListItem extends React.PureComponent {
_onPress = () => {
this.props.onPressItem(this.props.item);
};

<View style={{flex: 8}}>
<MatchList
data= {this.state.matches}
onPressItem= {this.navToMatchScreen}
/>
_onLongPress = () => {
this.props.onLongPressItem(this.props.item);
};

render() {
return (
<TouchableOpacity
onPress={this._onPress}
onLongPress={this._onLongPress}>
<View style={styles.cardItem}>
<Text style={styles.homeAddressItemText}>{this.props.item.name + ' : ' + this.props.item.cryptocurrency}</Text>
<Text style={styles.homeAddressItemText}>{this.props.balance ? 'Balance: ' + wei2Rounded(this.props.balance,4) : 'No balance found'}</Text>
<Text style={styles.homeAddressItemText}>{this.props.item.address}</Text>
</View>
</View>
)
</TouchableOpacity>
)
}
};
}

class AddressList extends React.PureComponent {
state = {
selected: (new Map(): Map<string, boolean>),
refreshing: false,
};

class MatchScreen extends React.Component {
_keyExtractor = (item, index) => item.address;

static navigationOptions = {
title: 'Match',
_onPressItem = (pair) => {
this.props.onPressItem(pair);
};

_onLongPressItem = (pair) => {
this.props.onLongPressItem(pair);
};
constructor(props) {

_onRefresh() {
this.setState({refreshing: true});
this.props.onRefresh();
//fetchData().then(() => {
this.setState({refreshing: false});
//});
}

_renderItem = ({item}) => (
<AddressListItem
props={item}
item={item}
balance={item.balance}
onPressItem={this._onPressItem}
onLongPressItem={this._onLongPressItem}
selected={!!this.state.selected.get(item.id)}
/>
);

render() {
return (
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}
/>
);
}
}


class AddressStatsScreen extends React.Component {
static navigationOptions = ({navigation}) => ({
title: `${navigation.state.params.cryptocurrency + ' : ' + navigation.state.params.name}`,
});

constructor(props){
super(props);
this.state = {
id: '',
score: [{red: '0', blue: '0'}],
balance: ' ',
avghashrate: null,
accountData: {},
currentHashratePercent: 0.0,
workers: [],
cryptocurrency: '',
address: '',
chartData: [],
miningChartHours: 6,
};
}

fetchStats() {
return fetch('https://logs.tf/json/' + (id? id.toString():'1486609'))
componentDidMount(){
this.fetchInfo();

//import the nav states to the main state
const { params } = this.props.navigation.state;

this.setState({
cryptocurrency: params.cryptocurrency,
address: params.address,
});
}

fetchInfo() {
const { params } = this.props.navigation.state;
//fetch balance
fetch('https://api.nanopool.org/v1/' + params.cryptocurrency.toString().toLowerCase() + '/balance/' + params.address.toString())
.then((response) => response.json())
.then((responseJson) => {
this.setState({
//add logs to the list
score: responseJson.teams
//update the balance state
balance: responseJson.data ? responseJson.data : 'Account not found',
}, function() {
//something to do w new states
// do something with new state
});
//got the balance
})
.catch((error) => {
console.error(error);
});

//fetch user overview data
fetch('https://api.nanopool.org/v1/' + params.cryptocurrency.toString().toLowerCase() + '/user/' + params.address.toString())
.then((response) => response.json())
.then((responseJson) => {
if(responseJson.data){
this.setState({
//update the balance state
accountData: responseJson.data,
avghashrate: responseJson.data.avgHashrate,
workers: responseJson.data.workers,
}, function() {
// do something with new state
this.parseAvgHashrates();
});
//console.log(responseJson.data);
}
})
.catch((error) => {
console.error(error);
});

//fetch account hashrate historical data
fetch('https://api.nanopool.org/v1/' + params.cryptocurrency.toString().toLowerCase() + '/hashratechart/' + params.address.toString())
.then((response) => response.json())
.then((responseJson) => {
if(responseJson.data){
this.setState({
//update the state
chartData: responseJson.data,
}, function() {
//do something wit fetched data
});
//console.log(responseJson.data);
}
})
.catch((error) => {
console.error(error);
});


}

@autobind
parseAvgHashrates(){
const { params } = this.props.navigation.state;

times = [1,3,6,12,24];
s = [];

//declare hash max and min at function scope
hashMax = null;
hashMin = null;
if(this.state.avghashrate){
//set first hashrate to both min and max
hashMax = parseInt(this.state.avghashrate['h' + times[0].toString()]);
hashMin = parseInt(this.state.avghashrate['h' + times[0].toString()]);
for(var i = 1;i < times.length; i++ ){

//next hashrate
h = parseInt(this.state.avghashrate['h' + times[i].toString()]);
hashMax = h > parseInt(hashMax) ? h : hashMax;
hashMin = h < parseInt(hashMin) ? h : hashMin;
}
h = parseInt(this.state.accountData.hashrate);
hashMax = h > parseInt(hashMax) ? h : hashMax;
hashMin = h < parseInt(hashMin) ? h : hashMin;

}

//generate the average hashrate line objects
for(var i = 0;i < times.length; i++ ){
s.push({
time: times[i].toString(),
rateText: (this.state.avghashrate ? Math.round(this.state.avghashrate['h' + times[i].toString()]) : '...') + HashrateUnits[params.cryptocurrency],
rateDispPercent: (this.state.avghashrate ? (this.state.avghashrate['h' + times[i].toString()]) : 0)/(hashMax ? hashMax : 1)
});
};
currPercent = (this.state.accountData.hashrate? this.state.avghashrate: 0)/(hashMax ? hashMax : 1);

this.setState(
(prev)=>{
return({
parsedHashrates: s,
currentHashratePercent: (this.state.accountData.hashrate)/(hashMax ? hashMax : 1),
})
},
(a)=>{

}
);
}

render(){
render() {
const { params } = this.props.navigation.state;
return (
<RefreshableScrollView
style={{flex:1, backgroundColor:'white' }}>
<View style={styles.cardItem}>
<Text style={styles.cardText}>
{'Balance : ' + this.state.balance + '\n' + params.address}
</Text>
</View>
<View style={styles.cardItem}>
<MiningChart
data={this.state.chartData}
hours={'24'}
height={100}
/>
</View>
<View style={styles.cardItem}>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{'Now'}</Text>
</View>
<View style={{flex: 3}}>
<Text>{'Current Hashrate:' + (this.state.accountData && this.state.accountData.hashrate)?this.state.accountData.hashrate:'nah'}</Text>
</View>
<View style={{flex: 8, margin: 2}}>
<HorizPercentBar
percent={this.state.currentHashratePercent}
barStyle={styles.nowHashBar}
barBackgroundStyle={styles.hashBarBG}/>
</View>

</View>
<AvgHashratesBarChart
hashrates={this.state.parsedHashrates}/>
</View>
<View style={styles.cardItem}>
<WorkerTable
data={this.state.workers}
hashrateUnit={HashrateUnits[this.state.cryptocurrency]}
/>
</View>
</RefreshableScrollView>
);
}
}

//const { navigate } = this.props.navigation;
class HorizPercentBar extends React.Component {
constructor(props) {
super(props);
this.state = {percent: 0};
}

render() {
return(

<View style={{backgroundColor: 'white'}}>
<Text>SCORE GOES HERE</Text>
</View>
<View style={{flexDirection: 'row',flex: 1}}>
<View style={[{flex: 10.0 - (this.props.percent * 10.0)}, this.props.barBackgroundStyle]}/>
<View style={[{flex: this.props.percent * 10.0}, this.props.barStyle]}/>
</View>
)
return null
}
}

class VertPercentBar extends React.Component {
constructor(props) {
super(props);
this.state = {percent: 0};
}

render() {
return(
<View style={{flex: 1,}}>
<View style={[{flex: 10.0 - (this.props.percent * 10.0)}, this.props.barBackgroundStyle]}/>
<View style={[{flex: this.props.percent * 10.0}, this.props.barStyle]}/>
</View>
)
return null
}
}

class MatchListItem extends React.PureComponent {
/**
props:
hashrates - array of hashrates objects
*/
class AvgHashratesBarChart extends React.Component {
constructor(props) {
super(props);
}


render() {
return (
<View>
{this.props.hashrates ? this.props.hashrates.map(function(rateSet,index){
return (
<View style={{flexDirection: 'row'}} key={index}>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{rateSet.time + 'h'}</Text>
</View>
<View style={{flex: 3}}>
<Text style={{textAlign: 'left',}}>{rateSet.rateText}</Text>
</View>
<View style={{flex: 8,margin: 2}}>
<HorizPercentBar
percent={rateSet.rateDispPercent}
barStyle={styles.avgHashBar}
barBackgroundStyle={styles.hashBarBG}/>
</View>
</View>
)})
: null
}
</View>
)
}
}

_onPress = () => {
//navigate('Match');
this.props.onPressItem({id:this.props.id});
console.log('match selected');
};

class WorkerTable extends React.Component {
constructor(props) {
super(props);
}

render() {
//const { navigate } = this.props.navigation;
return (
<TouchableOpacity onPress={this._onPress}>
<View style={styles.logItem}>
<Text style={styles.logItemText}>
{this.props.id}
</Text>
<Text style={styles.logItemText}>
{this.props.title}
</Text>
</View>
</TouchableOpacity>
<View>
<View style={{flexDirection: 'row', margin: 5}}>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{'Worker'}</Text>
</View>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{'Current'}</Text>
</View>
<View style={{flex: 1}}>
<Text style={{textAlign: 'right',}}>{'6h'}</Text>
</View>
<View style={{flex: 1,margin: 2}}>
<Text style={{textAlign: 'right',}}>{'12h'}</Text>
</View>
<View style={{flex: 2}}>
</View>
</View>
{this.props.data ? this.props.data.map(function(worker,index){
return (
<View style={{flexDirection: 'row', margin: 5}} key={index}>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{worker.id}</Text>
</View>
<View style={{flex: 1, marginRight: 5}}>
<Text style={{textAlign: 'right',}}>{worker.hashrate}</Text>
</View>
<View style={{flex: 1}}>
<Text style={{textAlign: 'right',}}>{worker.avg_h6}</Text>
</View>
<View style={{flex: 1,margin: 2}}>
<Text style={{textAlign: 'right',}}>{worker.avg_h12}</Text>
</View>
<View style={{flex: 2}}>
</View>
</View>
)})
: null
}
</View>
)
}
}

class MatchList extends React.PureComponent {
state = {selected: (new Map(): Map<string, boolean>)};
class MiningChart extends React.Component {
constructor(props){
super(props);
}

_keyExtractor = (item, index) => item.id;
render(){
//the data to plot
data = []
numPoints = this.props.data.length

//if a nonzero time range was supplied as a prop
if(this.props.hours){
//get number of data points to use (zero means use all)
numPoints = parseInt(this.props.hours) * 6

//take numPoints entries from the end of the array
data = this.props.data.slice(this.props.data.length - numPoints)
}else{
//default to all data if no time range supplied
data = this.props.data
}

//how many bars to display
numBars = this.props.bars ? this.props.bars : this.props.hours;

//in Mh/s
//console.log(data);
//hashrateData = data.map(function(a) {return Math.round(a.hashrate / 1000)});
hashrateData = data.map(function(a) {return Math.round(a.hashrate / 1000)});
sharesData = data.map(function(a) {return Math.round(a.shares)});

max = getMaxOfArray(hashrateData);
min = getMinOfArray(hashrateData);

//scale using max-min= 50%
mid = 0.5*(max+min);
fiftyRange = max-min;
breakPoint = mid - (fiftyRange);

//array to hold the bars' data
barData = [];
//how many data points each bar averages
incrementLength = hashrateData.length / numBars;

for(i = 0; i < numBars; i++){
thisBarData = hashrateData.slice(parseInt(i*incrementLength), (i + 1)*incrementLength);
barData.push(thisBarData);
// console.log(thisBarData);
}


//console.log(barData);

//console.log(arrToPercentOfMax(getMeanOfArrayRows(barData)));


barPercents = arrToPercentOfMax(getMeanOfArrayRows(barData));

return(<View style={{flex: 1}}>
<Text>{'Hashrate'}</Text>
<View style={{flexDirection: 'row', margin: 5, display: 'flex', height: this.props.height ? this.props.height : 100}}>
{barPercents ? barPercents.map(function(percent,index){
return (
<View
style={{flex: 1, margin: 2.5}}
key={index}>
<VertPercentBar style={{flex:1,}}
percent={percent}
barStyle={styles.avgHashBar}
barBackgroundStyle={styles.hashBarBG}
/>
</View>
)})
: null
}
</View>
<Text>{'Shares'}</Text>
<BarChart
data={sharesData}
bars={12}
height={100}
barStyle={styles.avgHashBar}
barBackgroundStyle={styles.hashBarBG}
/>
</View>
)

_onPressItem = (matchItem) => {

this.props.onPressItem(matchItem);
console.log('match list recieved touch');
};
}
}

_renderItem = ({item}) => (
<MatchListItem
id={item.id}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item.id)}
title={item.title}
/>
);
class BarChart extends React.Component {
constructor(props) {
super(props);
}

render() {
data = this.props.data;

numBars = this.props.bars ? this.props.bars : this.props.hours;

max = getMaxOfArray(data);
min = getMinOfArray(data);

//scale using max-min= 50%
mid = 0.5*(max+min);
fiftyRange = max-min;
breakPoint = mid - (fiftyRange);

//array to hold the bars' data
barData = [];
//how many data points each bar averages
incrementLength = hashrateData.length / numBars;

for(i = 0; i < numBars; i++){
thisBarData = data.slice(parseInt(i*incrementLength), (i + 1)*incrementLength);
barData.push(thisBarData);
// console.log(thisBarData);
}

barPercents = arrToPercentOfMax(getMeanOfArrayRows(barData));

console.log(barPercents);

return (
<View style={{flexDirection: 'row', margin: 5, display: 'flex', height: this.props.height ? this.props.height : 100}}>
{barPercents ? barPercents.map(function(percent,index){
console.log(percent);
return (
<View
style={{flex: 1, margin: 2.5}}
key={index}>
<VertPercentBar style={{flex:1,}}
percent={percent}
barStyle={styles.avgHashBar}
barBackgroundStyle={styles.hashBarBG}
/>
</View>
)})
: null
}
</View>
)
}
}

class RefreshableScrollView extends React.Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
};
}

fetchData(){
console.log('refreshing');
}

@autobind
_onRefresh() {
this.setState({refreshing: true});
this.setState({refreshing: false});
}

render() {
return (
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}
>
{this.props.children}
</ScrollView>
);
}
}

function getMaxOfArray(numArray) {
if (!numArray || numArray.length == 0) {
return null;
}
return Math.max.apply(null, numArray);
}

const styles = StyleSheet.create({
function getMinOfArray(numArray) {
if (!numArray || numArray.length == 0) {
return null;
}
return Math.min.apply(null, numArray);
}

logItem: {
justifyContent: 'center',
margin: 10,
borderRadius: 5,
borderColor: 'black',
borderWidth: 1,
backgroundColor: 'white',
},
function arrToPercentOfMax(arr) {
try {
max = getMaxOfArray(arr);
return arr.map((a) => {return (a*1.0) / (max*1.0)})
} catch(e){
console.log(e);
}
return null;
}

logItemText: {
textAlign: 'center',
function getMeanOfArray(arr){
if (!arr || arr.length == 0){
return null;
}
return arr.reduce(function(a,b){return a+b;}) / arr.length;
}

});
function getMeanOfArrayRows(arr){
try{
return arr.map((a) => {return getMeanOfArray(a)});
}catch(e){
console.log(e);
return null;
}
}

const App = StackNavigator({
/** Convert wei to ethereum
*/
function wei2Eth(wei){
w = parseFloat(wei);
return (w / (1e18));
}

/** Convert wei to ethereum and round at digits
*/
function wei2Rounded(wei,digits){
return (wei2Eth(wei)).toFixed(digits)
}

Home: { screen: HomeScreen },
Match: { screen: MatchScreen },
const App = StackNavigator({
Home: {
screen: HomeScreen,
navigationOptions: ({navigate}) => ({
title: 'NanoStats',
headerLeft: null,

}),
},
NewAddress: {
screen: NewAddressScreen,
},
AddressStats: {
screen: AddressStatsScreen,
},
});

export default App;
export default App;