279 lines
8.5 KiB
JavaScript
279 lines
8.5 KiB
JavaScript
import React from "react";
|
|
import {
|
|
ActivityIndicator,
|
|
StyleSheet,
|
|
View,
|
|
StatusBar,
|
|
Text,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
} from "react-native";
|
|
import themeColors from '../components/themeColors';
|
|
import EntypoIcon from "react-native-vector-icons/Entypo";
|
|
import AntDesignIcon from "react-native-vector-icons/AntDesign";
|
|
import FaIcon from "react-native-vector-icons/FontAwesome5";
|
|
import ImageHeader from "../components/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';
|
|
import {i18n} from '../utility/i18n';
|
|
|
|
|
|
function SessionCard(props) {
|
|
return (
|
|
<View style={sessionCardStyles.card}>
|
|
<View>
|
|
<Text style={sessionCardStyles.firstLineText}>{props.textFirstLine}</Text>
|
|
</View>
|
|
|
|
<View style={sessionCardStyles.secondLine}>
|
|
<View style={sessionCardStyles.iconTextPair}>
|
|
<FaIcon name="stopwatch" style={sessionCardStyles.icon} />
|
|
<Text style={sessionCardStyles.secondLineText}>{props.activeTime}</Text>
|
|
</View>
|
|
<View style={sessionCardStyles.iconTextPair}>
|
|
<EntypoIcon name="ruler" style={sessionCardStyles.icon} />
|
|
<Text style={sessionCardStyles.secondLineText}>{props.momentum}</Text>
|
|
</View>
|
|
<View style={sessionCardStyles.iconTextPair}>
|
|
<AntDesignIcon name="retweet" style={sessionCardStyles.icon} />
|
|
<Text style={sessionCardStyles.secondLineText}>{props.laps}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function SessionCardBehindSwipe(props) {
|
|
return (
|
|
<View style={sessionCardStyles.rowBack}>
|
|
<TouchableOpacity
|
|
style={sessionCardStyles.deleteButton}
|
|
onPress={props.onDelete}
|
|
>
|
|
<Text style={{ fontSize: 18, color: "white" }}>{i18n.t('delete_session_button')}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const sessionCardStyles = StyleSheet.create({
|
|
card: {
|
|
backgroundColor: "#559ac8",
|
|
borderRadius: 12,
|
|
height: 100,
|
|
maxHeight: 100,
|
|
flex: 1,
|
|
flexDirection: "column",
|
|
justifyContent: "space-around",
|
|
padding: 10,
|
|
margin: 10,
|
|
paddingLeft: 20,
|
|
},
|
|
firstLineText: {
|
|
color: "white",
|
|
fontSize: 22
|
|
},
|
|
iconTextPair: {
|
|
maxWidth: 100,
|
|
flex: 1,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
},
|
|
secondLine: {
|
|
flex: 1,
|
|
justifyContent: "space-between",
|
|
alignContent: "center",
|
|
flexDirection: "row",
|
|
maxHeight: 30,
|
|
marginTop: 14,
|
|
},
|
|
icon: {
|
|
fontSize: 30,
|
|
color: "white",
|
|
paddingRight: 10,
|
|
},
|
|
secondLineText: {
|
|
color: "white",
|
|
fontSize: 18,
|
|
},
|
|
spacerHidden: {
|
|
flex: 1,
|
|
color: "black",
|
|
},
|
|
rowBack: {
|
|
alignItems: 'center',
|
|
backgroundColor: themeColors['ALIZARIN'],
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
height: 100,
|
|
padding: 10,
|
|
margin: 10,
|
|
paddingLeft: 20,
|
|
borderRadius: 12,
|
|
},
|
|
deleteButton: {
|
|
alignItems: 'center',
|
|
bottom: 0,
|
|
justifyContent: 'center',
|
|
position: 'absolute',
|
|
backgroundColor: themeColors['ALIZARIN'],
|
|
top: 0,
|
|
width: 150,
|
|
right: 0,
|
|
borderTopRightRadius: 12,
|
|
borderBottomRightRadius: 12,
|
|
},
|
|
})
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
function parsePropfind(text) {
|
|
const parser = new XMLParser();
|
|
const xmlDoc = parser.parseFromString(text);
|
|
|
|
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);
|
|
}
|
|
//console.log("full data", parsed);
|
|
return parsed.reverse();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
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 => {
|
|
const filteredSession = this.state.sessions.filter((element) => element.name != sessionFileName);
|
|
this.setState({ sessions: filteredSession });
|
|
request({ url: "http://" + this.props.swimTrackerHost + "/webdav/" + sessionFileName, method: "DELETE" })
|
|
.then((value) => console.log("Successfully deleted", sessionFileName, value))
|
|
.catch((err) => console.error("Failed to delete", sessionFileName, err));
|
|
};
|
|
|
|
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}
|
|
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={Math.round(data.item.analysis.activeTime / 60, 0)} />
|
|
)}
|
|
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 barStyle="light-content" backgroundColor="rgba(0,0,0,0.4)" translucent={true} />
|
|
<View style={{ flex: 1 }}>
|
|
<ImageHeader
|
|
text={ i18n.t('lastSessions').toUpperCase() }
|
|
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);
|
|
|