Last sessions view
This commit is contained in:
parent
c6b517cfe4
commit
2d56390808
|
@ -25,8 +25,8 @@ export default class DataAnalysis {
|
|||
newData = allMeasurements;
|
||||
console.log("cache reset");
|
||||
}
|
||||
|
||||
const newDataArr = newData.toArray();
|
||||
const allMeasurementsSize = newData.size ? newData.size : newData.length;
|
||||
const newDataArr = (typeof newData.toArray ==="function") ? newData.toArray() : newData;
|
||||
|
||||
// active time
|
||||
const newAverages = this.movingAverage.addVector(newDataArr);
|
||||
|
@ -49,10 +49,10 @@ export default class DataAnalysis {
|
|||
const momentumWindow = windowed.reduce((sum, x) => sum + x, 0);
|
||||
|
||||
|
||||
this.analyzedUpToIdx = allMeasurements.size;
|
||||
this.analyzedUpToIdx = allMeasurementsSize;
|
||||
return {
|
||||
peaks: this.allPeaks,
|
||||
totalTime: allMeasurements.size / analysisParameters.numMeasurementsPerSec,
|
||||
totalTime: allMeasurementsSize / analysisParameters.numMeasurementsPerSec,
|
||||
activeTime: this.activeMeasurements / analysisParameters.numMeasurementsPerSec,
|
||||
|
||||
totalMomentum: this.aggregatedMomentum,
|
||||
|
|
|
@ -6631,6 +6631,11 @@
|
|||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
||||
|
@ -8161,6 +8166,11 @@
|
|||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"expo-keep-awake": "^8.1.0",
|
||||
"expo-linear-gradient": "~8.1.0",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"moment": "^2.27.0",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"msgpack5": "^4.2.1",
|
||||
"native-base": "2.13.8",
|
||||
|
@ -41,6 +42,7 @@
|
|||
"react-native-unimodules": "~0.8.1",
|
||||
"react-native-web": "^0.11.7",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-xml-parser": "^1.1.6",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"redux": "^4.0.5"
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
ActivityIndicator,
|
||||
StyleSheet,
|
||||
View,
|
||||
StatusBar,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import themeColors from './themeColors';
|
||||
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 ImageHeader from "./ImageHeader";
|
||||
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) {
|
||||
return (
|
||||
|
@ -43,7 +51,7 @@ function SessionCardBehindSwipe(props) {
|
|||
<View style={sessionCardStyles.rowBack}>
|
||||
<TouchableOpacity
|
||||
style={sessionCardStyles.deleteButton}
|
||||
onPress={() => deleteRow(rowMap, data.item.key)}
|
||||
onPress={props.onDelete}
|
||||
>
|
||||
<Text style={{ fontSize: 18, color: "white" }}>Löschen</Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -123,28 +131,120 @@ const sessionCardStyles = StyleSheet.create({
|
|||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
function parsePropfind(text) {
|
||||
const parser = new XMLParser();
|
||||
const xmlDoc = parser.parseFromString(text);
|
||||
|
||||
function LastSessionsView(props) {
|
||||
const data = [
|
||||
{
|
||||
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 parser = new DOMParser();
|
||||
//const xmlDoc = parser.parseFromString(text, "text/xml");
|
||||
|
||||
const renderHiddenItem = (data, rowMap) => (
|
||||
<SessionCardBehindSwipe />
|
||||
const responses = xmlDoc.getElementsByTagName("D:response");
|
||||
let result = [];
|
||||
for (let i = 0; i < responses.length; ++i) {
|
||||
const e = responses[i];
|
||||
const name = e.getElementsByTagName("D:href")[0].value;
|
||||
const size = e.getElementsByTagName("D:getcontentlength")[0].value;
|
||||
result.push({
|
||||
name: name,
|
||||
size: parseInt(size),
|
||||
startTime: parseInt(name.split(".")[0])
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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 }}>
|
||||
|
@ -152,31 +252,25 @@ function LastSessionsView(props) {
|
|||
<View style={{ flex: 1 }}>
|
||||
<ImageHeader
|
||||
text="LETZTE SESSIONS"
|
||||
navigation={props.navigation}
|
||||
navigation={this.props.navigation}
|
||||
image={require("../assets/swimmer.jpg")}
|
||||
/>
|
||||
<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}
|
||||
/>
|
||||
{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);
|
||||
|
||||
export default LastSessionsView;
|
|
@ -16,6 +16,7 @@ import { stopSession } from '../state/DeviceReduxCoupling';
|
|||
import CycleView from '../components/CycleView';
|
||||
import IconCard from '../components/IconCard';
|
||||
import Graph from '../components/Graph';
|
||||
import {toTimeStr} from '../utility/TimeUtils';
|
||||
|
||||
|
||||
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) {
|
||||
|
|
Loading…
Reference in New Issue