diff --git a/App.js b/App.js index 3588bce..f4c6ba8 100644 --- a/App.js +++ b/App.js @@ -10,11 +10,9 @@ import { DeviceReduxCoupling } from './state/DeviceReduxCoupling'; import { Provider } from 'react-redux'; import ThemedStackNavigation from './components/ThemedStackNavigation'; - +import NewAppMain from "./components/NewAppMain"; const store = createStore(swimtrackerReducer); -//const deviceReduxCoupling = new DeviceReduxCoupling(store); - export default class App extends React.Component { constructor(props) { @@ -38,11 +36,16 @@ export default class App extends React.Component { if (!this.state.isReady) { return ; } - + /* return ( + );*/ + return ( + + + ); } } diff --git a/assets/infinity_pool.jpg b/assets/infinity_pool.jpg new file mode 100644 index 0000000..49bd9ce Binary files /dev/null and b/assets/infinity_pool.jpg differ diff --git a/assets/infinity_pool2.jpg b/assets/infinity_pool2.jpg new file mode 100644 index 0000000..84acf29 Binary files /dev/null and b/assets/infinity_pool2.jpg differ diff --git a/assets/pool_sky_background_blurred.jpg b/assets/pool_sky_background_blurred.jpg new file mode 100644 index 0000000..071c614 Binary files /dev/null and b/assets/pool_sky_background_blurred.jpg differ diff --git a/components/NewAppMain.js b/components/NewAppMain.js new file mode 100644 index 0000000..218a9a5 --- /dev/null +++ b/components/NewAppMain.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; + +// Own views +import MainMenuView from "../views/MainMenuView"; +import SettingsView from "../views/SettingsView"; +import TrainingView from "../views/TrainingView"; + +const Stack = createStackNavigator(); + +function NewAppMain(props) { + + const screenOptions = { + headerShown: false, + } + + return ( + + + + + + + + ) +}; + +export default NewAppMain; diff --git a/data_processing/DataAnalysis.js b/data_processing/DataAnalysis.js index b632ac7..3703fa0 100644 --- a/data_processing/DataAnalysis.js +++ b/data_processing/DataAnalysis.js @@ -33,7 +33,6 @@ export default class DataAnalysis { this.activeMeasurements += newAverages.reduce((n, val) => { return n + ((val >= analysisParameters.activeTimeThreshold) ? 1 : 0); }, 0); - console.log("data", newDataArr, "newAverages", newAverages, "reduction", this.activeMeasurements); // peaks const newPeaks = this.peakDetectorSimple.addVector(newDataArr); @@ -63,7 +62,6 @@ export default class DataAnalysis { peakMaxWindow: peakMaxWindow, }; } - _resetCache(analysisParameters, sessionId) { this.movingAverage = analysisParameters ? new MovingAverage(analysisParameters.movingAverageWindowSize) : null; diff --git a/state/Reducer.js b/state/Reducer.js index 142804c..63c4edc 100644 --- a/state/Reducer.js +++ b/state/Reducer.js @@ -27,6 +27,7 @@ export const stopSession = () => ({ const INITIAL_SETTINGS = { theme: "hot", username: "", + //swimTrackerHost: "192.168.178.107", swimTrackerHost: "192.168.178.110", analysis: { diff --git a/views/LastSessionView.js b/views/LastSessionView.js new file mode 100644 index 0000000..e69de29 diff --git a/views/MainMenuView.js b/views/MainMenuView.js new file mode 100644 index 0000000..9410f7b --- /dev/null +++ b/views/MainMenuView.js @@ -0,0 +1,233 @@ +import React from "react"; +import { + StyleSheet, + View, + StatusBar, + ImageBackground, + Text, + TouchableOpacity, +} from "react-native"; +import themeColors from './themeColors'; +import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons"; +import EntypoIcon from "react-native-vector-icons/Entypo"; + +import { ConnState, startSession } from '../state/DeviceReduxCoupling'; +import { connect } from 'react-redux'; + + +// --------------------------------------------------------------------------------------------- + +function LargeHeaderView(props) { + return ( + + swimtracker + + bauer.tech + + ); +} + +const largeHeaderStyles = StyleSheet.create({ + container: { + height: 160, + maxHeight: 160, + flex: 1, + alignItems: "center", + justifyContent: "flex-end", + paddingBottom: 10, + //alignContent: "space-between" + }, + titleText: { + color: "rgba(255,255,255,1)", + fontSize: 48, + }, + subText: { + color: "rgba(255,255,255, 0.8)", + marginTop: 15, + textAlign: "center", + }, + separator: { + height: 7, + backgroundColor: "rgba(255,255,255,1)", + opacity: 0.5, + width: 230 + } +}); + +// --------------------------------------------------------------------------------------------- + +function ButtonGrid(props) { + return ( + + + + + {"LETZTE\nSESSIONS"} + + + + + + SOCIAL + + + + EINSTELLUNGEN + + + + ) +} + +const buttonGridStyles = StyleSheet.create({ + rowContainer: { + flex: 1, + flexDirection: "column", + justifyContent: "space-around", + alignItems: "center", + paddingTop: 20, + paddingBottom: 50, + }, + columnContainer: { + flex: 1, + width: "100%", + flexDirection: "row", + justifyContent: "space-around", + alignItems: "center", + }, + button: { + flex: 1, + margin: 20, + padding: 20, + width: 120, + height: 130, + borderRadius: 10, + opacity: 0.95, + justifyContent: "space-around", + alignItems: "center", + }, + buttonText: { + color: "rgba(255,255,255,1)", + textAlign: "center", + fontSize: 16, + }, + icon: { + color: "rgba(255,255,255,1)", + fontSize: 60, + } +}); + +// --------------------------------------------------------------------------------------------- + +function FullWidthButton(props) { + let textStyle = [fullWidthButtonStyles.buttonText]; + let iconStyle = [fullWidthButtonStyles.icon]; + if (props.disabled) { + textStyle.push(fullWidthButtonStyles.buttonTextDisabled); + iconStyle.push(fullWidthButtonStyles.iconDisabled); + } + return ( + + + + {props.text} + + + ) +} + +const fullWidthButtonStyles = StyleSheet.create({ + container: { + flex: 1, + maxHeight: 70, + backgroundColor: themeColors["WET ASPHALT"], + flexDirection: "row", + alignItems: "center", + //paddingBottom: 10, + justifyContent: "center", + }, + buttonText: { + padding: 10, + color: "rgba(255,255,255,1)", + fontSize: 25, + fontWeight: "600", + textAlign: "center", + }, + buttonTextDisabled: { + opacity: 0.3, + }, + icon: { + fontSize: 40, + padding: 10, + color: themeColors["GREEN SEA"], + }, + iconDisabled: { + color: themeColors["POMEGRANATE"], + }, +}); + + +// --------------------------------------------------------------------------------------------- + +function MainMenuView(props) { + const s = props.connState; + let startButtonText = "JETZT SCHWIMMEN"; + let startButtonDisabled = false; + if (s === ConnState.DISCONNECTED) { + startButtonText = "NICHT VERBUNDEN"; + startButtonDisabled = true; + } else if (s === ConnState.CONNECTED_RUNNING || s === ConnState.CONNECTED_STARTING) { + startButtonText = "TRAINING LÄUFT"; + } else if (s === ConnState.CONNECTED_STOPPING) { + startButtonDisabled = true; + } + + const onStartButtonPress = () => { + if (!props.connState !== ConnState.CONNECTED_RUNNING) { + props.dispatch(startSession()); + } + props.navigation.navigate('Training') + }; + + return ( + + + + + + props.navigation.navigate('Settings')} /> + + + + + ); +} + +const mapStateToProps = (state) => { + return { connState: state.deviceState.connState }; +}; + +export default connect(mapStateToProps)(MainMenuView); diff --git a/views/SettingsView.js b/views/SettingsView.js new file mode 100644 index 0000000..358dde2 --- /dev/null +++ b/views/SettingsView.js @@ -0,0 +1,207 @@ +import React, { useState } from "react"; +import { + StyleSheet, + View, + StatusBar, + TextInput, + ImageBackground, + Text, + TouchableOpacity, + SafeAreaView, + Slider, + Switch, +} from "react-native"; +import themeColors from './themeColors'; +import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons"; +import EntypoIcon from "react-native-vector-icons/Entypo"; + + +function ImageHeader(props) { + return ( + + + + props.navigation.goBack()}> + + + {props.text} + + + + ) +} + +const imageHeaderStyles = StyleSheet.create({ + container: { + flex: 1, + minHeight: 175, + maxHeight: 175, + height: 175, + width: "100%", + }, + row: { + paddingTop: 30, + flexDirection: "row", + }, + icon: { + color: "white", + fontSize: 40, + paddingRight: 10, + paddingLeft: 10, + }, + text: { + color: "white", + fontSize: 30, + }, +}); + +// --------------------------------------------------------------------------------------------- + +function SettingsTextInput(props) { + return ( + + {props.label} + + + ); +} + +function SettingsSwitch(props) { + const [isEnabled, setIsEnabled] = useState(false); + const toggleSwitch = () => setIsEnabled(previousState => !previousState); + + return ( + + {props.label} + + + ) +} + +function SettingsSlider(props) { + return ( + + {props.label} + + + ) +} + +function SettingsCombo(props) { + +} + + +function SettingsGroup(props) { + + return ( + + {props.title} + + {React.Children.map(props.children, (child, idx) => + + {child} + + )} + + + ); +}; + +const settingsGroupStyles = StyleSheet.create({ + container: { + padding: 20, + paddingRight: 30, + }, + title: { + color: "white", + fontSize: 20, + fontWeight: "600", + }, + subsettings: { + padding: 10, + paddingLeft: 30, + }, + row: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + borderTopColor: "rgba(255, 255,255, 0.6)", + paddingTop: 5, + paddingBottom: 5, + borderTopWidth: 0.5, + minHeight: 40, + }, + firstRow: { + paddingTop: 0, + borderTopWidth: 0, + }, + label: { + color: "white", + fontSize: 17, + }, + textInput: { + color: "rgba(255,255,255,1)", + marginTop: 8, + }, + slider: { + minWidth: 100, + width: 150, + //minHeight: 50, + }, + switch: { + //minHeight: 50, + //minWidth: 80, + } +}); + +// --------------------------------------------------------------------------------------------- + + +function SettingsView(props) { + return ( + + + ) +} + +export default SettingsView; \ No newline at end of file diff --git a/views/TrainingView.js b/views/TrainingView.js new file mode 100644 index 0000000..ea6f0bc --- /dev/null +++ b/views/TrainingView.js @@ -0,0 +1,132 @@ +import React, { Component } from "react"; +import { + StyleSheet, + View, + StatusBar, + Text, + TouchableOpacity, + SafeAreaView +} from "react-native"; +import themeColors from './themeColors'; +import EntypoIcon from "react-native-vector-icons/Entypo"; + +import { connect } from 'react-redux'; +import { useKeepAwake } from 'expo-keep-awake'; +import { stopSession } from '../state/DeviceReduxCoupling'; +import CycleView from '../components/CycleView'; +import IconCard from '../components/IconCard'; +import Graph from '../components/Graph'; + + +function SmallHeaderView(props) { + return ( + + + props.navigation.goBack()}> + + + {props.text} + + + + + + ) +} + +const smallHeaderStyles = StyleSheet.create({ + container: { + flex: 1, + minHeight: 80, + maxHeight: 80, + height: 80, + width: "100%", + backgroundColor: themeColors["WET ASPHALT"], + }, + row: { + paddingTop: 30, + flexDirection: "row", + justifyContent: "space-between", + }, + backIcon: { + color: "white", + fontSize: 40, + }, + stopIcon: { + color: themeColors["ALIZARIN"], + fontSize: 40, + paddingRight: 10, + paddingLeft: 10, + }, + text: { + color: "white", + fontSize: 30, + }, +}); + +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) { + useKeepAwake(); + + const analysis = props.session.analysis; + const laps = (analysis.peaks.size / props.peaksPerLap).toFixed(1); + const totalMomentum = Math.trunc(analysis.totalMomentum * props.kgFactor / 10 / 60); + + const onStopPressed = () => { + props.dispatch(stopSession()); + props.navigation.navigate('Home'); + }; + + return ( + + + ) +} + +const trainingViewStyles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: themeColors["BELIZE HOLE"], + padding: 20, + justifyContent: "space-around", + } +}); + +const mapStateToProps = (state) => { + return { + session: state.deviceState, + peaksPerLap: state.settings.analysis.peaksPerLap, + theme: state.settings.theme, + kgFactor: state.settings.analysis.kgFactor, + }; +}; +export default connect(mapStateToProps)(TrainingView); diff --git a/views/themeColors.js b/views/themeColors.js new file mode 100644 index 0000000..5f2854c --- /dev/null +++ b/views/themeColors.js @@ -0,0 +1,26 @@ +// https://flatuicolors.com/palette/defo + +const themeColors = { + 'TURQUOISE': "rgb(26, 188, 156)", + "EMERALD": "rgb(46, 204, 113)", + "PETER RIVER" : "rgb(52, 152, 219)", + "AMETHYST" : "rgb(155, 89, 182)", + "WET ASPHALT" : "rgb(52, 73, 94)", + "GREEN SEA" : "rgb(22, 160, 133)", + "NEPHRITIS" : "rgb(39, 174, 96)", + "BELIZE HOLE" : "rgb(41, 128, 185)", + "WISTERIA" : "rgb(142, 68, 173)", + "MIDNIGHT BLUE" : "rgb(44, 62, 80)", + "SUN FLOWER" : "rgb(241, 196, 15)", + "CARROT" : "rgb(230, 126, 34)", + "ALIZARIN" : "rgb(231, 76, 60)", + "CLOUDS" : "rgb(236, 240, 241)", + "CONCRETE" : "rgb(149, 165, 166)", + "ORANGE" : "rgb(243, 156, 18)", + "PUMPKIN" : "rgb(211, 84, 0)", + "POMEGRANATE" : "rgb(192, 57, 43)", + "SILVER" : "rgb(189, 195, 199)", + "ASBESTOS" : "rgb(127, 140, 141)", +}; + +export default themeColors;