Last sessions view
This commit is contained in:
parent
c6b517cfe4
commit
2d56390808
|
@ -25,8 +25,8 @@ export default class DataAnalysis {
|
||||||
newData = allMeasurements;
|
newData = allMeasurements;
|
||||||
console.log("cache reset");
|
console.log("cache reset");
|
||||||
}
|
}
|
||||||
|
const allMeasurementsSize = newData.size ? newData.size : newData.length;
|
||||||
const newDataArr = newData.toArray();
|
const newDataArr = (typeof newData.toArray ==="function") ? newData.toArray() : newData;
|
||||||
|
|
||||||
// active time
|
// active time
|
||||||
const newAverages = this.movingAverage.addVector(newDataArr);
|
const newAverages = this.movingAverage.addVector(newDataArr);
|
||||||
|
@ -49,10 +49,10 @@ export default class DataAnalysis {
|
||||||
const momentumWindow = windowed.reduce((sum, x) => sum + x, 0);
|
const momentumWindow = windowed.reduce((sum, x) => sum + x, 0);
|
||||||
|
|
||||||
|
|
||||||
this.analyzedUpToIdx = allMeasurements.size;
|
this.analyzedUpToIdx = allMeasurementsSize;
|
||||||
return {
|
return {
|
||||||
peaks: this.allPeaks,
|
peaks: this.allPeaks,
|
||||||
totalTime: allMeasurements.size / analysisParameters.numMeasurementsPerSec,
|
totalTime: allMeasurementsSize / analysisParameters.numMeasurementsPerSec,
|
||||||
activeTime: this.activeMeasurements / analysisParameters.numMeasurementsPerSec,
|
activeTime: this.activeMeasurements / analysisParameters.numMeasurementsPerSec,
|
||||||
|
|
||||||
totalMomentum: this.aggregatedMomentum,
|
totalMomentum: this.aggregatedMomentum,
|
||||||
|
|
|
@ -6631,6 +6631,11 @@
|
||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||||
|
},
|
||||||
"morgan": {
|
"morgan": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
||||||
|
@ -8161,6 +8166,11 @@
|
||||||
"tween-functions": "^1.0.1"
|
"tween-functions": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-xml-parser": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-xml-parser/-/react-xml-parser-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-m/DU8CIXMJ3KvSAacgGhXYeTtfUKc8XCju/xAlncv3+uXTaOmuSpZI1z1ZTpV6PkDXre+1SJQRyRZY8N5MUp4g=="
|
||||||
|
},
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"expo-keep-awake": "^8.1.0",
|
"expo-keep-awake": "^8.1.0",
|
||||||
"expo-linear-gradient": "~8.1.0",
|
"expo-linear-gradient": "~8.1.0",
|
||||||
"immutable": "^4.0.0-rc.12",
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"moment": "^2.27.0",
|
||||||
"msgpack-lite": "^0.1.26",
|
"msgpack-lite": "^0.1.26",
|
||||||
"msgpack5": "^4.2.1",
|
"msgpack5": "^4.2.1",
|
||||||
"native-base": "2.13.8",
|
"native-base": "2.13.8",
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
"react-native-unimodules": "~0.8.1",
|
"react-native-unimodules": "~0.8.1",
|
||||||
"react-native-web": "^0.11.7",
|
"react-native-web": "^0.11.7",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
|
"react-xml-parser": "^1.1.6",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"redux": "^4.0.5"
|
"redux": "^4.0.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,7 +31,7 @@ const INITIAL_SETTINGS = {
|
||||||
swimTrackerHost: "192.168.178.110", // testgeraet
|
swimTrackerHost: "192.168.178.110", // testgeraet
|
||||||
|
|
||||||
analysis: {
|
analysis: {
|
||||||
peaksPerLap: 30,
|
peaksPerLap: 30,
|
||||||
windowSizeInSecs: 5,
|
windowSizeInSecs: 5,
|
||||||
numMeasurementsPerSec: 10,
|
numMeasurementsPerSec: 10,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
let request = obj => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(obj.method || "GET", obj.url);
|
||||||
|
if (obj.headers) {
|
||||||
|
Object.keys(obj.headers).forEach(key => {
|
||||||
|
xhr.setRequestHeader(key, obj.headers[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(obj.responseType) {
|
||||||
|
xhr.responseType = obj.responseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
resolve(xhr.response);
|
||||||
|
} else {
|
||||||
|
reject(xhr.statusText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = () => reject(xhr.statusText);
|
||||||
|
xhr.send(obj.body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default request;
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
import moment from 'moment/min/moment-with-locales';
|
||||||
|
|
||||||
|
|
||||||
|
function timeSince(timeStamp, lang = 'de') {
|
||||||
|
moment.locale(lang);
|
||||||
|
|
||||||
|
const now = Math.floor((new Date()).getTime() / 1000);
|
||||||
|
const secondsPast = now - timeStamp;
|
||||||
|
if (secondsPast <= 6 * 3600) {
|
||||||
|
return moment().seconds(-secondsPast).fromNow();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const timeStampDate = new Date(timeStamp * 1000);
|
||||||
|
const dateNow = new Date();
|
||||||
|
|
||||||
|
const timeStampMoment = moment.unix(timeStamp);
|
||||||
|
|
||||||
|
let dateStr = "";
|
||||||
|
|
||||||
|
if (timeStampDate.getDate() == dateNow.getDate())
|
||||||
|
dateStr = "Heute, " + timeStampMoment.format("HH:mm");
|
||||||
|
else if (timeStampDate.getDate() + 1 == dateNow.getDate())
|
||||||
|
dateStr = "Gestern, " + timeStampMoment.format("HH:mm");
|
||||||
|
else {
|
||||||
|
dateStr = timeStampMoment.format("ddd, DD.MM.YY um HH:mm");
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const toTimeStr = seconds => {
|
||||||
|
let minuteStr = String(Math.floor(seconds / 60));
|
||||||
|
if (minuteStr.length < 2)
|
||||||
|
minuteStr = "0" + minuteStr;
|
||||||
|
let secondStr = String(Math.floor(seconds % 60));
|
||||||
|
if (secondStr.length < 2)
|
||||||
|
secondStr = "0" + secondStr;
|
||||||
|
return minuteStr + ":" + secondStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export { toTimeStr, timeSince };
|
|
@ -1,10 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
RefreshControl,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import themeColors from './themeColors';
|
import themeColors from './themeColors';
|
||||||
import EntypoIcon from "react-native-vector-icons/Entypo";
|
import EntypoIcon from "react-native-vector-icons/Entypo";
|
||||||
|
@ -12,6 +14,12 @@ import AntDesignIcon from "react-native-vector-icons/AntDesign";
|
||||||
import FaIcon from "react-native-vector-icons/FontAwesome5";
|
import FaIcon from "react-native-vector-icons/FontAwesome5";
|
||||||
import ImageHeader from "./ImageHeader";
|
import ImageHeader from "./ImageHeader";
|
||||||
import { SwipeListView } from 'react-native-swipe-list-view';
|
import { SwipeListView } from 'react-native-swipe-list-view';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import request from '../utility/PromiseRequest';
|
||||||
|
import DataAnalysis from '../data_processing/DataAnalysis';
|
||||||
|
import * as msgpack from 'msgpack-lite';
|
||||||
|
import { timeSince } from '../utility/TimeUtils';
|
||||||
|
import XMLParser from 'react-xml-parser';
|
||||||
|
|
||||||
function SessionCard(props) {
|
function SessionCard(props) {
|
||||||
return (
|
return (
|
||||||
|
@ -43,7 +51,7 @@ function SessionCardBehindSwipe(props) {
|
||||||
<View style={sessionCardStyles.rowBack}>
|
<View style={sessionCardStyles.rowBack}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={sessionCardStyles.deleteButton}
|
style={sessionCardStyles.deleteButton}
|
||||||
onPress={() => deleteRow(rowMap, data.item.key)}
|
onPress={props.onDelete}
|
||||||
>
|
>
|
||||||
<Text style={{ fontSize: 18, color: "white" }}>Löschen</Text>
|
<Text style={{ fontSize: 18, color: "white" }}>Löschen</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
@ -123,60 +131,146 @@ const sessionCardStyles = StyleSheet.create({
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function parsePropfind(text) {
|
||||||
|
const parser = new XMLParser();
|
||||||
|
const xmlDoc = parser.parseFromString(text);
|
||||||
|
|
||||||
function LastSessionsView(props) {
|
//const parser = new DOMParser();
|
||||||
const data = [
|
//const xmlDoc = parser.parseFromString(text, "text/xml");
|
||||||
{
|
|
||||||
textFirstLine: "Gestern 19:12 Uhr",
|
|
||||||
laps: "31",
|
|
||||||
activeTime: "26:13",
|
|
||||||
laps: "35",
|
|
||||||
momentum: "120",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
textFirstLine: "Montag 18:10 Uhr",
|
|
||||||
laps: "27",
|
|
||||||
activeTime: "26:13",
|
|
||||||
laps: "35",
|
|
||||||
momentum: "120",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const renderHiddenItem = (data, rowMap) => (
|
const responses = xmlDoc.getElementsByTagName("D:response");
|
||||||
<SessionCardBehindSwipe />
|
let result = [];
|
||||||
);
|
for (let i = 0; i < responses.length; ++i) {
|
||||||
|
const e = responses[i];
|
||||||
return (
|
const name = e.getElementsByTagName("D:href")[0].value;
|
||||||
<View style={{ flex: 1 }}>
|
const size = e.getElementsByTagName("D:getcontentlength")[0].value;
|
||||||
<StatusBar hidden={true} />
|
result.push({
|
||||||
<View style={{ flex: 1 }}>
|
name: name,
|
||||||
<ImageHeader
|
size: parseInt(size),
|
||||||
text="LETZTE SESSIONS"
|
startTime: parseInt(name.split(".")[0])
|
||||||
navigation={props.navigation}
|
});
|
||||||
image={require("../assets/swimmer.jpg")}
|
}
|
||||||
/>
|
return result;
|
||||||
<View style={{ flex: 1, backgroundColor: themeColors["BELIZE HOLE"] }}>
|
|
||||||
<SwipeListView
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
disableRightSwipe="true"
|
|
||||||
data={data}
|
|
||||||
renderItem={(data, rowMap) => (
|
|
||||||
<SessionCard
|
|
||||||
textFirstLine={data.item.textFirstLine}
|
|
||||||
laps={data.item.laps}
|
|
||||||
momentum={data.item.momentum}
|
|
||||||
activeTime={data.item.activeTime}
|
|
||||||
laps={data.item.laps} />
|
|
||||||
)}
|
|
||||||
renderHiddenItem={renderHiddenItem}
|
|
||||||
leftOpenValue={0}
|
|
||||||
rightOpenValue={-120}
|
|
||||||
stopRightSwipe={-145}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LastSessionsView;
|
const msgpackCodec = msgpack.createCodec();
|
||||||
|
msgpackCodec.addExtUnpacker(205, function (byteArr) {
|
||||||
|
const buffer = byteArr.buffer.slice(byteArr.byteOffset, byteArr.byteLength + byteArr.byteOffset);
|
||||||
|
const result = new Int16Array(buffer);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function getSessionDetails(swimTrackerHost, sessionFileName) {
|
||||||
|
const url = "http://" + swimTrackerHost + "/webdav/" + sessionFileName;
|
||||||
|
const arrayBuffer = await request({ url: url, responseType: 'arraybuffer' });
|
||||||
|
return msgpack.decode(new Uint8Array(arrayBuffer), { codec: msgpackCodec });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSessionsFromDevice(swimTrackerHost) {
|
||||||
|
const data = await request({ url: "http://" + swimTrackerHost + "/webdav/", method: "PROPFIND" });
|
||||||
|
return parsePropfind(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFullData(swimTrackerHost, analysisSettings) {
|
||||||
|
const parsed = await getSessionsFromDevice(swimTrackerHost);
|
||||||
|
for (let index = 0; index < parsed.length; index++) {
|
||||||
|
const e = parsed[index];
|
||||||
|
const sessionDetails = await getSessionDetails(swimTrackerHost, e.name);
|
||||||
|
e.values = sessionDetails.values;
|
||||||
|
const da = new DataAnalysis();
|
||||||
|
e.analysis = da.analyze(analysisSettings, e.startTime, e.values);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LastSessionsView extends React.Component {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = { sessions: null, refreshing: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
getFullData(this.props.swimTrackerHost, this.props.analysisSettings).then(
|
||||||
|
e => this.setState({ sessions: e })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const deleteSession = async sessionFileName => {
|
||||||
|
this.setState({ sessions: null });
|
||||||
|
await request({ url: "http://" + this.props.swimTrackerHost + "/webdav/" + sessionFileName, method: "DELETE" });
|
||||||
|
this.setState({ sessions: await getFullData(this.props.swimTrackerHost, this.props.analysisSettings) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
this.setState({ refreshing: true });
|
||||||
|
const newSessions = await getFullData(this.props.swimTrackerHost, this.props.analysisSettings);
|
||||||
|
this.setState({ sessions: newSessions, refreshing: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
let innerView;
|
||||||
|
if (this.state.sessions) {
|
||||||
|
innerView = <SwipeListView
|
||||||
|
refreshControl={<RefreshControl refreshing={this.state.refreshing} onRefresh={onRefresh} /> }
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
keyExtractor={item => item.startTime.toString()}
|
||||||
|
disableRightSwipe={true}
|
||||||
|
data={this.state.sessions.reverse()}
|
||||||
|
renderItem={(data, rowMap) => (
|
||||||
|
<SessionCard
|
||||||
|
textFirstLine={timeSince(data.item.startTime)}
|
||||||
|
laps={(data.item.analysis.peaks.size / this.props.peaksPerLap).toFixed(1)}
|
||||||
|
momentum={Math.trunc(data.item.analysis.totalMomentum * this.props.kgFactor / 10 / 60)}
|
||||||
|
activeTime={data.item.analysis.activeTime} />
|
||||||
|
)}
|
||||||
|
renderHiddenItem={(data, rowMap) => <SessionCardBehindSwipe onDelete={() => { deleteSession(data.item.name) }} />}
|
||||||
|
leftOpenValue={0}
|
||||||
|
rightOpenValue={-120}
|
||||||
|
stopRightSwipe={-145}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
innerView = (
|
||||||
|
<View style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-around",
|
||||||
|
}}>
|
||||||
|
<ActivityIndicator size="large" color="#aaa"></ActivityIndicator>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<StatusBar hidden={true} />
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<ImageHeader
|
||||||
|
text="LETZTE SESSIONS"
|
||||||
|
navigation={this.props.navigation}
|
||||||
|
image={require("../assets/swimmer.jpg")}
|
||||||
|
/>
|
||||||
|
<View style={{ flex: 1, backgroundColor: themeColors["BELIZE HOLE"] }}>
|
||||||
|
{innerView}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
swimTrackerHost: state.settings.swimTrackerHost,
|
||||||
|
analysisSettings: state.settings.analysis,
|
||||||
|
kgFactor: state.settings.analysis.kgFactor,
|
||||||
|
peaksPerLap: state.settings.analysis.peaksPerLap,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default connect(mapStateToProps)(LastSessionsView);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { stopSession } from '../state/DeviceReduxCoupling';
|
||||||
import CycleView from '../components/CycleView';
|
import CycleView from '../components/CycleView';
|
||||||
import IconCard from '../components/IconCard';
|
import IconCard from '../components/IconCard';
|
||||||
import Graph from '../components/Graph';
|
import Graph from '../components/Graph';
|
||||||
|
import {toTimeStr} from '../utility/TimeUtils';
|
||||||
|
|
||||||
|
|
||||||
function SmallHeaderView(props) {
|
function SmallHeaderView(props) {
|
||||||
|
@ -64,16 +65,6 @@ const smallHeaderStyles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const toTimeStr = seconds => {
|
|
||||||
let minuteStr = String(Math.floor(seconds / 60));
|
|
||||||
if (minuteStr.length < 2)
|
|
||||||
minuteStr = "0" + minuteStr;
|
|
||||||
let secondStr = String(Math.floor(seconds % 60));
|
|
||||||
if (secondStr.length < 2)
|
|
||||||
secondStr = "0" + secondStr;
|
|
||||||
return minuteStr + ":" + secondStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
function TrainingView(props) {
|
function TrainingView(props) {
|
||||||
|
|
Loading…
Reference in New Issue