swimtracker-app/SwimTracker/views/LastSessionsView.js

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);