app changes
This commit is contained in:
@@ -1,48 +1,58 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import msgpack5 from 'msgpack5';
|
||||
|
||||
|
||||
/*
|
||||
config = {
|
||||
peakDetector: 'simple',
|
||||
peakDetectorConfig: {'threshold': 3000},
|
||||
|
||||
deviceUrl: 'http://smartswim',
|
||||
peaksPerLap: 30,
|
||||
forceAverage
|
||||
}
|
||||
*/
|
||||
import * as msgpack from 'msgpack-lite';
|
||||
|
||||
class DeviceHttpDataSource extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = [];
|
||||
this.dataUrl = new URL("/api/session/data", this.props.deviceUrl);
|
||||
this.dataUrl = this.props.deviceUrl + "/api/session/data";
|
||||
|
||||
// msgpack setup
|
||||
this.msgpack = msgpack5();
|
||||
this.msgpack.registerDecoder(205, function (byteArr) {
|
||||
this.msgpackCodec = msgpack.createCodec();
|
||||
this.msgpackCodec.addExtUnpacker(205, function (byteArr) {
|
||||
const buffer = byteArr.buffer.slice(byteArr.byteOffset, byteArr.byteLength + byteArr.byteOffset);
|
||||
return new Int16Array(buffer);
|
||||
const result = new Int16Array(buffer);
|
||||
return result;
|
||||
});
|
||||
|
||||
this.fetchDataHttp = this.fetchDataHttp.bind(this);
|
||||
}
|
||||
|
||||
|
||||
getUrl(url) {
|
||||
return new Promise((accept, reject) => {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", url, true);
|
||||
req.responseType = "arraybuffer";
|
||||
|
||||
req.onload = function (event) {
|
||||
var resp = req.response;
|
||||
if (resp) {
|
||||
accept(resp);
|
||||
}
|
||||
};
|
||||
req.send(null);
|
||||
});
|
||||
//todo reject on error
|
||||
}
|
||||
|
||||
async fetchDataHttp() {
|
||||
this.dataUrl.searchParams.set("startIdx", this.data.length);
|
||||
const response = await fetch(this.dataUrl);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const decoded = this.msgpack.decode(arrayBuffer);
|
||||
const typedValueArr = decoded['values'];
|
||||
console.log("new data inside fetch", typedValueArr);
|
||||
const newDataStart = this.data.length;
|
||||
for (let i = 0; i < typedValueArr.length; ++i) {
|
||||
this.data.push(typedValueArr[i]);
|
||||
try {
|
||||
const url = this.dataUrl + "?startIdx=" + this.data.length;
|
||||
const arrayBuffer = await this.getUrl(url);
|
||||
const decoded = msgpack.decode(new Uint8Array(arrayBuffer), { codec: this.msgpackCodec });
|
||||
|
||||
const typedValueArr = decoded['values'];
|
||||
const newDataStart = this.data.length;
|
||||
for (let i = 0; i < typedValueArr.length; ++i) {
|
||||
this.data.push(typedValueArr[i]);
|
||||
}
|
||||
this.props.onNewData(this.data, newDataStart);
|
||||
} catch (err) {
|
||||
//console.log(err);
|
||||
}
|
||||
this.props.onNewData(this.data, newDataStart);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -55,7 +65,7 @@ class DeviceHttpDataSource extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import {View, StyleSheet, Text} from 'react-native';
|
||||
|
||||
import Svg, {Polyline, Polygon, Rect, G} from 'react-native-svg-web';
|
||||
//import Svg, {Polyline, Polygon, Rect, G} from 'react-native-svg-web';
|
||||
import Svg, {Polyline, Polygon, Rect, G} from 'react-native-svg';
|
||||
|
||||
|
||||
const Graph = props => {
|
||||
@@ -11,7 +12,6 @@ const Graph = props => {
|
||||
|
||||
const coordStr = data.map((element, i) => `${i}, ${element / 2}`);
|
||||
|
||||
|
||||
return (
|
||||
<View style={{justifyContent: 'center', alignItems: 'center'}}>
|
||||
<Svg height={graphHeight} width="80%" viewbox="0 0 100 100">
|
||||
|
||||
56
components/HomeView.js
Normal file
56
components/HomeView.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { Content, Card, CardItem, Body, Text, Button } from 'native-base';
|
||||
import { Image, ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { startSession} from '../state/ActionCreators';
|
||||
|
||||
function HomeView(props) {
|
||||
const buttonText = props.running ? "View Swim Session" : "Start swimming";
|
||||
|
||||
const onButtonPress = () => {
|
||||
if(!props.running) {
|
||||
props.dispatch(startSession());
|
||||
}
|
||||
props.navigation.navigate('Training')
|
||||
};
|
||||
|
||||
return (
|
||||
<Content padder contentContainerStyle={{ justifyContent: 'space-around', flex: 1, marginTop: 70 }}>
|
||||
<ScrollView styles={{ marginHorizontal: 20, paddingTop: 100 }} alwaysBounceHorizontal={true} alwaysBounceVertical={true}>
|
||||
<Card style={{ backgroundColor: "transparent" }}>
|
||||
<CardItem cardBody>
|
||||
<Image source={require('../assets/pool-water.jpg')} style={{ height: 100, width: null, flex: 1 }} />
|
||||
</CardItem>
|
||||
|
||||
<CardItem style={{backgroundColor: 'rgba(255, 255, 255, 0.6)'}}>
|
||||
<Body>
|
||||
<Button block onPress={onButtonPress}>
|
||||
<Text>{buttonText}</Text>
|
||||
</Button>
|
||||
</Body>
|
||||
</CardItem>
|
||||
</Card>
|
||||
|
||||
<Card style={{ backgroundColor: "transparent" }}>
|
||||
<CardItem cardBody>
|
||||
<Image source={require('../assets/blue-water-background.jpg')} style={{ height: 100, width: null, flex: 1 }} />
|
||||
</CardItem>
|
||||
|
||||
<CardItem style={{backgroundColor: 'rgba(255, 255, 255, 0.6)'}}>
|
||||
<Body>
|
||||
<Button block >
|
||||
<Text>Settings</Text>
|
||||
</Button>
|
||||
</Body>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</ScrollView>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return { running: state.session.running };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(HomeView);
|
||||
@@ -6,13 +6,14 @@ const IconCard = props => {
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<View style={{ alignItems: 'center', justifyContent: 'center', paddingLeft: 20 }}>
|
||||
|
||||
<View style={{ paddingLeft: 20 }}>
|
||||
<Text style={{ color: 'white', fontSize: props.fontSize, textAlign: "center" }}> {props.value}</Text>
|
||||
</View>
|
||||
<View style={{ alignItems: 'center', justifyContent: 'center', paddingLeft: 20 }}>
|
||||
<Icon style={{ color: 'white', fontSize: 40 }} name={props.iconName} type={props.iconType} />
|
||||
<Text style={{ color: 'white', marginTop: 5 }}> {props.label}</Text>
|
||||
</View>
|
||||
<View style={{ paddingRight: 20 }}>
|
||||
<Text style={{ color: 'white', fontSize: props.fontSize }}> {props.value}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -29,7 +30,8 @@ const styles = StyleSheet.create({
|
||||
});
|
||||
|
||||
IconCard.defaultProps = {
|
||||
fontSize: 85
|
||||
fontSize: 85,
|
||||
flex: 1
|
||||
};
|
||||
|
||||
export default IconCard;
|
||||
|
||||
111
components/LiveTrainingView.js
Normal file
111
components/LiveTrainingView.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Container, Text, Header, Content, Left, Body, Right, Button, Icon, Title, Card, CardItem, Fab} from 'native-base';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import IconCard from './IconCard';
|
||||
import Graph from './Graph';
|
||||
import DeviceHttpDataSource from './DeviceHttpDataSource';
|
||||
import { PeakDetectorSimple } from '../data_processing/PeakDetection';
|
||||
|
||||
|
||||
|
||||
export default class LiveTrainingView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isReady: false,
|
||||
themeNumber: 0,
|
||||
numPeaks: 0,
|
||||
numLaps: 0,
|
||||
measurements: []
|
||||
};
|
||||
|
||||
this.config = {
|
||||
deviceUrl: "http://smartswim",
|
||||
peakThreshold: 30,
|
||||
peaksPerLap: 30,
|
||||
updateInterval: 3000,
|
||||
};
|
||||
|
||||
this.peakDetector = new PeakDetectorSimple(this.config.peakThreshold, peaks => {
|
||||
//console.log("peaks:", peaks.length);
|
||||
this.setState({
|
||||
numPeaks: peaks.length,
|
||||
numLaps: (peaks.length / this.config.peaksPerLap).toFixed(1)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
handleStart = () => {
|
||||
fetch(this.config.deviceUrl + "/api/session/start").catch(err => console.log(err));
|
||||
}
|
||||
|
||||
handleStop = () => {
|
||||
fetch(this.config.deviceUrl + "/api/session/stop").catch(err => console.log(err));
|
||||
}
|
||||
|
||||
handleThemeChange = () => {
|
||||
this.setState((state, props) => { return { themeNumber: ((state.themeNumber + 1) % themeArray.length) } })
|
||||
}
|
||||
|
||||
handleNewData = (fullData, newDataStart) => {
|
||||
const newData = fullData.slice(newDataStart);
|
||||
//console.log("New data", newData.length, "Full data", fullData.length, "new data start", newDataStart);
|
||||
//console.log("new data", newData);
|
||||
this.peakDetector.addVector(newData);
|
||||
this.setState({ measurements: fullData });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Content padder contentContainerStyle={{ justifyContent: 'space-around', flex: 1 }}>
|
||||
<IconCard label="BAHNEN" value="9" iconName="retweet" iconType="AntDesign" fontSize={110} />
|
||||
<IconCard label="ZÜGE" value="800" iconName="dashboard" iconType="AntDesign" />
|
||||
<IconCard label="KRAFT" value="120" iconName="ruler" iconType="Entypo" />
|
||||
{/*
|
||||
<IconCard label="ZÜGE" value={this.state.numPeaks} iconName="dashboard" iconType="AntDesign" />
|
||||
<IconCard label="BAHNEN" value={this.state.numLaps} iconName="retweet" iconType="AntDesign" />
|
||||
|
||||
<IconCard label="KRAFT" value="120" iconName="ruler" iconType="Entypo" />
|
||||
*/}
|
||||
|
||||
</Content>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const backgroundColors = {
|
||||
'hot': ['#830e5f', '#fd5139'],
|
||||
'darkBlue': ['#4265a3', '#cfada7'],
|
||||
'lightBlue': ['#50a4db', '#74bbe2'],
|
||||
'foggy': ['#bc8db8', '#5d5e90'],
|
||||
};
|
||||
|
||||
const themeArray = [
|
||||
'hot', 'darkBlue', 'lightBlue', 'foggy'
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
margin: 5,
|
||||
padding: 5,
|
||||
borderRadius: 3,
|
||||
justifyContent: 'space-between',
|
||||
/*
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 1,
|
||||
},
|
||||
shadowOpacity: 0.18,
|
||||
shadowRadius: 1.00,
|
||||
|
||||
elevation: 1,*/
|
||||
}
|
||||
});
|
||||
70
components/ThemedStackNavigation.js
Normal file
70
components/ThemedStackNavigation.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import backgroundColors from './Themes';
|
||||
|
||||
// Own views
|
||||
import LiveTrainingView from './LiveTrainingView';
|
||||
import HomeView from './HomeView';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
|
||||
function ThemedStackNavigation(props) {
|
||||
|
||||
const screenOptions = {
|
||||
cardStyle: {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 1
|
||||
},
|
||||
headerTransparent: "true",
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
fontSize: "1.5em",
|
||||
},
|
||||
headerTintColor: "white",
|
||||
headerBackground: () => (
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={30}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={backgroundColors[props.themeName]}
|
||||
start={[0, 0]}
|
||||
end={[0.5, 1]}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator initialRouteName="Home">
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
component={HomeView}
|
||||
options={{ ...screenOptions, headerTitle: "SwimTracker" }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Training"
|
||||
component={LiveTrainingView}
|
||||
options={{ ...screenOptions, headerTitle: "Training" }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</LinearGradient>
|
||||
)
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return { themeName: state.settings.theme };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(ThemedStackNavigation);
|
||||
9
components/Themes.js
Normal file
9
components/Themes.js
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
const backgroundColors = {
|
||||
'hot': ['#830e5f', '#fd5139'],
|
||||
'darkBlue': ['#4265a3', '#cfada7'],
|
||||
'lightBlue': ['#50a4db', '#74bbe2'],
|
||||
'foggy': ['#bc8db8', '#5d5e90'],
|
||||
};
|
||||
|
||||
export default backgroundColors;
|
||||
Reference in New Issue
Block a user