New setup from scratch - all modules updated - app now in subfolder
|
@ -1 +0,0 @@
|
|||
{}
|
|
@ -1,14 +0,0 @@
|
|||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
web-report/
|
||||
/dist
|
||||
venv
|
||||
__pycache__
|
|
@ -1 +0,0 @@
|
|||
{}
|
|
@ -0,0 +1,3 @@
|
|||
/node_modules
|
||||
/.expo
|
||||
.directory
|
|
@ -13,12 +13,6 @@ import { persistStore, persistReducer } from 'redux-persist'
|
|||
import hardSet from 'redux-persist/lib/stateReconciler/hardSet'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
||||
// Internationalization
|
||||
import * as Localization from 'expo-localization';
|
||||
import i18n from 'i18n-js';
|
||||
import en from "./locales/en/translations";
|
||||
import de from "./locales/de/translations";
|
||||
|
||||
// Navigation
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
@ -32,16 +26,6 @@ import ConnectingView from './views/ConnectingView';
|
|||
import WifiSelectionView from './views/WifiSelectionView';
|
||||
import WifiPasswordView from './views/WifiPasswordView';
|
||||
|
||||
|
||||
// Set the key-value pairs for the different languages you want to support.
|
||||
i18n.translations = {
|
||||
en: en,
|
||||
de: de,
|
||||
};
|
||||
i18n.locale = Localization.locale; // Set the locale once at the beginning of your app.
|
||||
//i18n.locale = "en-US";
|
||||
i18n.fallbacks = true; // When a value is missing from a language it'll fallback to another language with the key present.
|
||||
|
||||
const persistConfig = {
|
||||
key: 'root',
|
||||
storage: AsyncStorage,
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"expo": {
|
||||
"name": "SwimTracker",
|
||||
"slug": "SwimTracker",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-localization"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
> Why do I have a folder named ".expo" in my project?
|
||||
|
||||
The ".expo" folder is created when an Expo project is started using "expo start" command.
|
||||
|
||||
> What does the "packager-info.json" file contain?
|
||||
|
||||
The "packager-info.json" file contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
|
||||
|
||||
> What does the "settings.json" file contain?
|
||||
|
||||
The "settings.json" file contains the server configuration that is used to serve the application manifest.
|
||||
|
||||
> Should I commit the ".expo" folder?
|
||||
|
||||
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
|
||||
|
||||
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
|
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 419 KiB After Width: | Height: | Size: 419 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 340 KiB |
Before Width: | Height: | Size: 602 KiB After Width: | Height: | Size: 602 KiB |
Before Width: | Height: | Size: 740 KiB After Width: | Height: | Size: 740 KiB |
Before Width: | Height: | Size: 899 KiB After Width: | Height: | Size: 899 KiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
@ -0,0 +1,109 @@
|
|||
import SwimTrackerWebsocketConnection from "../data_processing/SwimTrackerWebsocketConnection.js";
|
||||
import yargs from 'yargs';
|
||||
import WS from 'ws';
|
||||
import { SerialPort } from 'serialport'
|
||||
import { ReadlineParser } from '@serialport/parser-readline'
|
||||
import chalk from 'chalk';
|
||||
|
||||
|
||||
/**
|
||||
* Opens serial port and calls provided function on each line
|
||||
*
|
||||
* @param {string} serialPortPath
|
||||
* @param {(str) => any} onLineReceived
|
||||
*/
|
||||
function openSerialConnection(serialPortPath, onLineReceived) {
|
||||
const port = new SerialPort({
|
||||
path: serialPortPath,
|
||||
baudRate: 115200
|
||||
});
|
||||
const parser = port.pipe(new ReadlineParser());
|
||||
parser.on('data', onLineReceived);
|
||||
}
|
||||
|
||||
function basicTest(deviceIpOrHostname, serialPort) {
|
||||
|
||||
let lastHostLog = undefined;
|
||||
let lastDeviceLog = undefined;
|
||||
|
||||
function log(color, main, startCol) {
|
||||
if(lastHostLog === undefined)
|
||||
lastHostLog = Date.now();
|
||||
const currentTime = Date.now();
|
||||
const dt = currentTime - lastHostLog;
|
||||
lastHostLog = currentTime;
|
||||
|
||||
console.log(color((startCol).padEnd(9), "|", String(dt).padStart(5), "|", main));
|
||||
}
|
||||
|
||||
function logDeviceTime(color, main, column1, time) {
|
||||
if(lastDeviceLog === undefined)
|
||||
lastDeviceLog = time;
|
||||
const dt = time - lastDeviceLog;
|
||||
lastDeviceLog = time;
|
||||
|
||||
console.log(color((column1).padEnd(9), "|", String(dt).padStart(5), "|", main));
|
||||
}
|
||||
|
||||
const onStarted = (sessionid) => { log(chalk.yellow, "Session started " + sessionid, "WS Msg") };
|
||||
const onStopped = () => { log(chalk.yellow, "Session stopped", "WS Msg") };
|
||||
const onWifiStateInfo = (wifiInfo) => { log(chalk.cyan, JSON.stringify(wifiInfo), "WS Wifi") };
|
||||
const onWebsocketLog = (logMsg) => {logDeviceTime(chalk.blue, logMsg.msg, "WS Log", String(logMsg.time))};
|
||||
const onDisconnect = () => { log(chalk.red, "Disconnected", "WS Msg") };
|
||||
const onConnect = () => {
|
||||
log(chalk.blue,"Connected to device", "WS Msg")
|
||||
conn.sendLogStreamStartCommand();
|
||||
};
|
||||
|
||||
let data = [];
|
||||
|
||||
const onNewMeasurementData = (newDataAsUint16Arr) => {
|
||||
const arr = Array.from(newDataAsUint16Arr);
|
||||
newDataAsUint16Arr.forEach(element => {
|
||||
data.push(element);
|
||||
});
|
||||
|
||||
const existing = "(" + String(data.length).padStart(5) + ") ";
|
||||
if(arr.length > 5) {
|
||||
log(chalk.yellow, existing + "Bulk " + arr.length + " measurements", "WS Data");
|
||||
} else {
|
||||
log(chalk.gray, existing + JSON.stringify(Array.from(newDataAsUint16Arr)), "WS Data");
|
||||
}
|
||||
};
|
||||
|
||||
if(serialPort) {
|
||||
openSerialConnection(serialPort, (receivedLine) => {
|
||||
log(chalk.gray, receivedLine, "Serial");
|
||||
});
|
||||
}
|
||||
|
||||
const conn = new SwimTrackerWebsocketConnection(deviceIpOrHostname, onNewMeasurementData, onStarted, onStopped,
|
||||
onWifiStateInfo, onConnect, onDisconnect,
|
||||
{ WebSocket: WS });
|
||||
conn.onLogMessage = onWebsocketLog;
|
||||
}
|
||||
|
||||
|
||||
basicTest("192.168.178.56", "/dev/ttyUSB1");
|
||||
//basicTest("192.168.178.56", undefined);
|
||||
|
||||
// {"speeds":[0.4, -0.3],"durations":[1500, 1500]}
|
||||
|
||||
/*
|
||||
Testrun:
|
||||
Captures:
|
||||
- streamed data from websocket
|
||||
- log messages from serial port
|
||||
- output of serial port
|
||||
File format:
|
||||
- timestamp of aquisition
|
||||
- type
|
||||
- websocket message
|
||||
- serial port line
|
||||
Logging:
|
||||
- websocket errors always
|
||||
|
||||
Commands:
|
||||
- replay file
|
||||
- data as csv
|
||||
*/
|
|
@ -15,6 +15,7 @@ const OpCodes = {
|
|||
WIFI_STATE_RESPONSE: 8,
|
||||
WIFI_SCAN_RESPONSE: 9,
|
||||
APP_LAYER_PING: 10,
|
||||
LOG_UPDATE: 11,
|
||||
|
||||
// from frontend to device
|
||||
START_SESSION: 128,
|
||||
|
@ -25,13 +26,26 @@ const OpCodes = {
|
|||
WIFI_STATE_SET: 133,
|
||||
WIFI_STATE_GET: 134,
|
||||
WIFI_TRIGGER_SCAN: 135,
|
||||
LOG_STREAMING_START: 136,
|
||||
LOG_STREAMING_STOP: 137
|
||||
};
|
||||
|
||||
const HEARTBEAT_TIMEOUT = 3000;
|
||||
const PROVISIONING_IP = "192.168.42.1";
|
||||
|
||||
export default class SwimTrackerWebsocketConnection {
|
||||
constructor(swimTrackerHost, onData, onStarted, onStopped, onWifiStateInfo, onConnect, onDisconnect) {
|
||||
/**
|
||||
* Creates a new persistent websocket connection to a swimtracker device
|
||||
*
|
||||
* @param {string} swimTrackerHost hostname or ip of the swimtracker device
|
||||
* @param {(data: Uint16Array) => any} onData called whenever new measurement data is available
|
||||
* @param {(sessionId: number) => any} onStarted called when a new measurement session was started
|
||||
* @param {() => any} onStopped called when session was stopped
|
||||
* @param {(wifistate : object) => any} onWifiStateInfo wifi state contains "state" (STATION_MODE|AP_PROVISIONING|AP_SECURE) and "hostname"
|
||||
* @param {() => any} onConnect called when websocket connection was established
|
||||
* @param {() => any} onDisconnect called when websocket disconnected
|
||||
*/
|
||||
constructor(swimTrackerHost, onData, onStarted, onStopped, onWifiStateInfo, onConnect, onDisconnect, reconnectingWsOptions={}) {
|
||||
this.swimTrackerHost = swimTrackerHost;
|
||||
|
||||
this.onData = onData;
|
||||
|
@ -40,14 +54,14 @@ export default class SwimTrackerWebsocketConnection {
|
|||
this.onWifiStateInfo = onWifiStateInfo;
|
||||
this.onConnect = onConnect;
|
||||
this.onDisconnect = onDisconnect;
|
||||
|
||||
this.onLogMessage = () => {};
|
||||
|
||||
// try configured URL and provisioning URL
|
||||
const urls = [`ws://${swimTrackerHost}:81`, `ws://${PROVISIONING_IP}:81`];
|
||||
let urlIndex = 0;
|
||||
const urlProvider = () => urls[urlIndex++ % urls.length]; // round robin url provider
|
||||
|
||||
this.ws = new ReconnectingWebSocket(urlProvider, [], { maxReconnectionDelay: 3000 });
|
||||
|
||||
this.ws = new ReconnectingWebSocket(urlProvider, [], { ...reconnectingWsOptions, maxReconnectionDelay: 3000 });
|
||||
this.ws.onmessage = this._onMessage;
|
||||
this.ws.onopen = this.onConnect;
|
||||
this.ws.onclose = this.onDisconnect;
|
||||
|
@ -98,6 +112,14 @@ export default class SwimTrackerWebsocketConnection {
|
|||
this._sendMsg(OpCodes.TARE);
|
||||
}
|
||||
|
||||
sendLogStreamStartCommand = () => {
|
||||
this._sendMsg(OpCodes.LOG_STREAMING_START);
|
||||
}
|
||||
|
||||
sendLogStreamStopCommand = () => {
|
||||
this._sendMsg(OpCodes.LOG_STREAMING_STOP);
|
||||
}
|
||||
|
||||
scanWifiNetworks() {
|
||||
console.log("Trigger wifi scan");
|
||||
this._sendMsg(OpCodes.WIFI_TRIGGER_SCAN);
|
||||
|
@ -177,6 +199,9 @@ export default class SwimTrackerWebsocketConnection {
|
|||
} else if (opCode === OpCodes.WIFI_STATE_RESPONSE) {
|
||||
const wifiInfo = msgpack.decode(payload, { codec: this.msgpackCodec });
|
||||
this.onWifiStateInfo(wifiInfo);
|
||||
} else if (opCode === OpCodes.LOG_UPDATE) {
|
||||
const logMsg = msgpack.decode(payload, { codec: this.msgpackCodec });
|
||||
this.onLogMessage(logMsg);
|
||||
} else if (opCode === OpCodes.APP_LAYER_PING) {
|
||||
//console.log("got heartbeat");
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "swimtracker",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/webpack-config": "^19.0.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"@react-navigation/native": "^6.1.8",
|
||||
"@react-navigation/stack": "^6.3.18",
|
||||
"expo": "~49.0.13",
|
||||
"expo-app-loading": "^2.1.1",
|
||||
"expo-localization": "~14.3.0",
|
||||
"expo-status-bar": "~1.6.0",
|
||||
"i18n-js": "^4.3.2",
|
||||
"immutable": "^5.0.0-beta.4",
|
||||
"moment": "^2.29.4",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-reanimated": "~3.3.0",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.0",
|
||||
"react-native-svg": "13.9.0",
|
||||
"react-native-swipe-list-view": "^3.2.9",
|
||||
"react-native-web": "~0.19.6",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-xml-parser": "^1.1.8",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"redux": "^4.2.1",
|
||||
"redux-persist": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { I18n } from "i18n-js";
|
||||
import * as Localization from 'expo-localization';
|
||||
|
||||
const translation_store = {
|
||||
en: {
|
||||
connecting: "Connecting",
|
||||
connectSubtext: "Please connect your phone to the WiFi of your SwimTracker",
|
||||
simpleMode: "Simple Mode",
|
||||
advancedMode: "Advanced Mode",
|
||||
help: "Need help?",
|
||||
settings: "Settings",
|
||||
lastSessions: "Last Sessions",
|
||||
mainMenu_social: "Social",
|
||||
mainMenu_swimNow: "Swim now"
|
||||
},
|
||||
de : {
|
||||
connecting: "Verbindung aufbauen",
|
||||
connectSubtext: "Gehe entweder in das WLAN deines SwimTrackers oder in dein eigenes WLAN, falls du den SwimTracker schon eingerichtet hast.",
|
||||
simpleMode: "Weniger Einstellungen",
|
||||
advancedMode: "Mehr Einstellungen",
|
||||
settings: "Einstellungen",
|
||||
help: "Hilfe",
|
||||
lastSessions: "Letzte Sessions",
|
||||
mainMenu_social: "Freunde",
|
||||
mainMenu_swimNow: "Jetzt schwimmen",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const i18n = new I18n();
|
||||
i18n.store(translation_store);
|
||||
i18n.defaultLocale = "en";
|
||||
i18n.enableFallback = true;
|
||||
i18n.locale = Localization.locale;
|
|
@ -10,7 +10,7 @@ import {
|
|||
import SetupView from '../components/SetupView';
|
||||
import { connect } from 'react-redux';
|
||||
import { changeSwimTrackerHostname } from '../state/Reducer';
|
||||
import i18n from 'i18n-js';
|
||||
import { i18n } from '../utility/i18n';
|
||||
|
||||
const validHostnameRegex = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/;
|
||||
|
|
@ -20,7 +20,7 @@ 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 'i18n-js';
|
||||
import {i18n} from '../utility/i18n';
|
||||
|
||||
|
||||
function SessionCard(props) {
|
|
@ -13,7 +13,7 @@ import MaterialCommIcon from "react-native-vector-icons/MaterialCommunityIcons";
|
|||
|
||||
import EntypoIcon from "react-native-vector-icons/Entypo";
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
import { i18n } from '../utility/i18n';
|
||||
|
||||
import { ConnState, startSession } from '../state/DeviceReduxCoupling';
|
||||
import { connect } from 'react-redux';
|
|
@ -8,13 +8,11 @@ import {
|
|||
Switch,
|
||||
} from "react-native";
|
||||
import themeColors from '../components/themeColors';
|
||||
import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import EntypoIcon from "react-native-vector-icons/Entypo";
|
||||
import ImageHeader from "../components/ImageHeader";
|
||||
import { connect } from 'react-redux';
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import request from '../utility/PromiseRequest';
|
||||
import i18n from 'i18n-js';
|
||||
import { i18n } from '../utility/i18n';
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
35
app.json
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"expo": {
|
||||
"name": "SwimTracker",
|
||||
"slug": "swimtracker",
|
||||
"privacy": "public",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android",
|
||||
"web"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "cover",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"icon": "./assets/icon-ios.png",
|
||||
"bundleIdentifier": "tech.bauer.swimtracker"
|
||||
},
|
||||
"description": "",
|
||||
"android": {
|
||||
"package": "tech.bauer.swimtracker"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ PUBLIC_URL = "https://swimtracker.bauer.tech"
|
|||
DEPLOY_HOST = "server"
|
||||
DEPLOY_PATH = "/volumes/swimtracker"
|
||||
|
||||
|
||||
APP_UPDATE_FOLDER = "app-update"
|
||||
APP_FOLDER = "app"
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# Environment Setup
|
||||
|
||||
as of 2023-09
|
||||
|
||||
|
||||
1. nodejs
|
||||
---------
|
||||
|
||||
- Downloaded version `node-v18.18.0-linux-x64` and put the `node-v18.18.0-linux-x64/bin` into the path via `~/.config/fish/config.fish`
|
||||
- Installed yarn as package manager (Source `https://yarnpkg.com/getting-started/install`)
|
||||
- `corepack enable`
|
||||
|
||||
2. expo
|
||||
-------
|
||||
|
||||
(Source `https://reactnative.dev/docs/environment-setup`)
|
||||
|
||||
- `yarn create expo-app SwimTracker`
|
||||
|
||||
- Starting with `yarn start -w` doesn't work and suggests runnig
|
||||
`npx expo install react-native-web react-dom @expo/webpack-config`
|
||||
|
||||
|
||||
3. Install Dependencies
|
||||
-----------------------
|
||||
use `npx expo install` instead of `yarn add` !
|
||||
|
||||
|
||||
# Redux stuff
|
||||
- react-redux
|
||||
- redux-persist
|
||||
- @react-native-async-storage/async-storage
|
||||
|
||||
# Translation
|
||||
i18n-js
|
||||
expo-localization
|
||||
|
||||
|
||||
react-native-gesture-handler
|
||||
react-native-reanimated
|
||||
expo-app-loading # TODO deprecated
|
||||
@react-navigation/native
|
||||
@react-navigation/stack
|
||||
react-native-safe-area-context # to not cover status bar etc
|
||||
react-native-screens # not used directly, but somehow required
|
||||
|
||||
# Data handling
|
||||
msgpack-lite
|
||||
immutable
|
||||
reconnecting-websocket
|
||||
|
||||
# Webdav reponse parsing
|
||||
react-xml-parser
|
||||
|
||||
|
||||
# Graph
|
||||
react-native-svg
|
||||
|
|
@ -8,7 +8,6 @@ npm -g install turtle-cli --legacy-peer-deps
|
|||
expo export --public-url https://swimtracker.bauer.tech/app-update
|
||||
scp -r dist/* root@server:"/volumes/swimtracker/app-update/"
|
||||
|
||||
|
||||
# Needs jdk 8
|
||||
apt install openjdk-8-jdk-headless
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk-amd64/bin/:$PATH
|
|
@ -1,11 +0,0 @@
|
|||
export default {
|
||||
connecting: "Verbindung aufbauen",
|
||||
connectSubtext: "Gehe entweder in das WLAN deines SwimTrackers oder in dein eigenes WLAN, falls du den SwimTracker schon eingerichtet hast.",
|
||||
simpleMode: "Weniger Einstellungen",
|
||||
advancedMode: "Mehr Einstellungen",
|
||||
settings: "Einstellungen",
|
||||
help: "Hilfe",
|
||||
lastSessions: "Letzte Sessions",
|
||||
mainMenu_social: "Freunde",
|
||||
mainMenu_swimNow: "Jetzt schwimmen",
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
export default {
|
||||
connecting: "Connecting",
|
||||
connectSubtext: "Please connect your phone to the WiFi of your SwimTracker",
|
||||
simpleMode: "Simple Mode",
|
||||
advancedMode: "Advanced Mode",
|
||||
help: "Need help?",
|
||||
settings: "Settings",
|
||||
lastSessions: "Last Sessions",
|
||||
mainMenu_social: "Social",
|
||||
mainMenu_swimNow: "Swim now"
|
||||
}
|
58
package.json
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"test": "jest"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo",
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/native": "^5.4.2",
|
||||
"@react-navigation/stack": "^5.3.9",
|
||||
"expo": "^41.0.0",
|
||||
"expo-app-loading": "^1.0.3",
|
||||
"expo-blur": "~9.0.3",
|
||||
"expo-keep-awake": "~9.1.2",
|
||||
"expo-linear-gradient": "~9.1.0",
|
||||
"expo-localization": "~10.1.0",
|
||||
"i18n-js": "^3.8.0",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"moment": "^2.27.0",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"msgpack5": "^4.2.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "0.63.4",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-reanimated": "~2.1.0",
|
||||
"react-native-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"react-native-svg": "12.1.0",
|
||||
"react-native-svg-web": "^1.0.7",
|
||||
"react-native-swipe-list-view": "^3.2.3",
|
||||
"react-native-unimodules": "~0.13.3",
|
||||
"react-native-web": "~0.13.12",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-xml-parser": "^1.1.6",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"redux": "^4.0.5",
|
||||
"redux-persist": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-expo": "8.3.0",
|
||||
"jest-expo": "^41.0.0",
|
||||
"react-test-renderer": "^16.13.1"
|
||||
},
|
||||
"private": true
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import React, { Component } from 'react'
|
||||
import { View, PanResponder, GestureResponderEvent } from 'react-native'
|
||||
import Svg, {
|
||||
Circle,
|
||||
Ellipse,
|
||||
G,
|
||||
LinearGradient,
|
||||
RadialGradient,
|
||||
Line,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Symbol,
|
||||
Use,
|
||||
Defs,
|
||||
Stop
|
||||
} from 'react-native-svg';
|
||||
|
||||
|
||||
|
||||
export default class Foo extends Component {
|
||||
|
||||
panResponder = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { x: 200, y: 200, initX: 0, initY: 0 }
|
||||
|
||||
this.panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
||||
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
onPanResponderGrant: () => {
|
||||
|
||||
},
|
||||
|
||||
onPanResponderStart: (evt, gestureState) => {
|
||||
console.log("start", gestureState);
|
||||
this.setState({ initX: this.state.x, initY: this.state.y });
|
||||
},
|
||||
|
||||
onPanResponderMove: (evt, gs) => {
|
||||
//console.log(gs.dx + ' ' + gs.dy)
|
||||
|
||||
const newX = this.state.initX + gs.dx;
|
||||
const newY = this.state.initY + gs.dy;
|
||||
|
||||
this.setState({ x: newX, y: newY });
|
||||
},
|
||||
onPanResponderTerminationRequest: (evt, gestureState) => true,
|
||||
|
||||
onPanResponderRelease: (evt, gs) => {
|
||||
console.log('Release ' + gs.dx + ' ' + gs.dy);
|
||||
//this.setState({ x: this.state.x, y: 0 });
|
||||
},
|
||||
onShouldBlockNativeResponder: (evt, gestureState) => {
|
||||
// Returns whether this component should block native components from becoming
|
||||
// the JS responder. Returns true by default. Is currently only supported on
|
||||
// android.
|
||||
return true;
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<Svg height="500" width="500">
|
||||
<Circle
|
||||
{...this.panResponder.panHandlers}
|
||||
x={this.state.x}
|
||||
y={this.state.y}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="20"
|
||||
stroke="blue"
|
||||
strokeWidth="3.5"
|
||||
fill="white" />
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
import React, { useRef } from "react";
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
StatusBar,
|
||||
ImageBackground,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
Animated,
|
||||
PanResponder,
|
||||
} from "react-native";
|
||||
import EntypoIcon from "react-native-vector-icons/Entypo";
|
||||
import Svg, { G, Polyline, Line, Circle, Rect, Text as SvgText } from 'react-native-svg';
|
||||
|
||||
|
||||
|
||||
let AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
||||
let AnimatedG = Animated.createAnimatedComponent(G);
|
||||
|
||||
function SliderDraftView(props) {
|
||||
const pan = useRef(new Animated.ValueXY()).current;
|
||||
|
||||
/*
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onPanResponderTerminationRequest: () => { console.log("p1"); return false; },
|
||||
onStartShouldSetPanResponder: () => { console.log("p2"); return true; },
|
||||
onMoveShouldSetPanResponder: () => { console.log("p3"); return true; },
|
||||
//onPanResponderMove: Animated.event([
|
||||
// null,
|
||||
// { dx: pan.x, dy: pan.y }
|
||||
//]),
|
||||
onPanResponderMove: (e, gesture) => {
|
||||
console.log(gesture);
|
||||
},
|
||||
onPanResponderRelease: () => {
|
||||
console.log("release");
|
||||
Animated.spring(pan, { toValue: { x: 0, y: 0 } }).start();
|
||||
}
|
||||
})
|
||||
);//.current;
|
||||
*/
|
||||
const panResponder = useRef(PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onMoveShouldSetPanResponderCapture: () => true,
|
||||
onPanResponderMove: (_, { dx, dy }) => {
|
||||
console.log("bla", dx, dy);
|
||||
cx.setValue(dx);
|
||||
cy.setValue(dy);
|
||||
setCurrentPoint({ x: dx, y: dy });
|
||||
},
|
||||
onPanResponderRelease: (e, { dx, dy }) => {
|
||||
console.log("release", dx, dy);
|
||||
cx.extractOffset();
|
||||
cy.extractOffset();
|
||||
offsetX = offsetX + dx;
|
||||
offsetY = offsetY + dy;
|
||||
}
|
||||
})).current;
|
||||
|
||||
|
||||
console.log({ ...panResponder.panHandlers });
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
|
||||
<StatusBar barStyle="light-content" backgroundColor="rgba(0,0,0,0.4)" translucent={true} />
|
||||
<ImageBackground
|
||||
source={require("../assets/pool_sky_background_blurred.jpg")}
|
||||
resizeMode="cover"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<View style={setupViewStyles.container}>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
{props.backButton &&
|
||||
<TouchableOpacity onPress={() => props.navigation.goBack()}>
|
||||
<EntypoIcon name="chevron-left" style={setupViewStyles.backButton}></EntypoIcon>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
<Text style={setupViewStyles.headerText}>
|
||||
Slider Draft
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<Svg height="100%" width="100%" viewBox="0 0 200 200" {...panResponder.panHandler}>
|
||||
<Rect x="10" y="10" rx="15" width="160" height="40" fill="rgba(255, 255, 255, 0.7)" />
|
||||
<SvgText x="85" y="34" fill="rgba(80, 80, 80, 1)">42</SvgText>
|
||||
|
||||
|
||||
<AnimatedCircle
|
||||
x={pan.x} y={pan.y} r="20"
|
||||
></AnimatedCircle>
|
||||
|
||||
<G >
|
||||
<Line x1="25" y1="20" x2="25" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="45" y1="20" x2="45" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="65" y1="20" x2="65" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="85" y1="20" x2="85" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="105" y1="20" x2="105" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="125" y1="20" x2="125" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
<Line x1="145" y1="20" x2="145" y2="40" stroke="rgba(80, 80, 80, 0.7)" />
|
||||
</G>
|
||||
</Svg>
|
||||
</View>
|
||||
</View>
|
||||
</ImageBackground>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const setupViewStyles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "space-between",
|
||||
width: "80%",
|
||||
marginLeft: 40,
|
||||
marginTop: 60,
|
||||
},
|
||||
headerText: {
|
||||
color: "rgba(255,255,255,1)",
|
||||
fontSize: 25,
|
||||
},
|
||||
subtext: {
|
||||
color: "rgba(255,255,255,1)",
|
||||
textAlign: "left",
|
||||
fontSize: 18,
|
||||
lineHeight: 25,
|
||||
width: "80%",
|
||||
marginBottom: 50,
|
||||
},
|
||||
backButton: {
|
||||
color: "rgba(255,255,255,1)",
|
||||
fontSize: 40
|
||||
},
|
||||
});
|
||||
|
||||
export default SliderDraftView;
|
|
@ -0,0 +1,22 @@
|
|||
# %%
|
||||
import msgpack
|
||||
|
||||
#data = [130, 164, 116, 105, 109, 101, 206, 0, 7, 200, 131, 163, 109, 115, 103, 217, 56, 110, 101, 119, 32, 119, 101, 98, 115, 111, 99, 107, 101, 116, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 44, 32, 115, 116, 111, 114, 105, 110, 103, 32, 97, 116, 32, 112, 111, 115, 32, 49, 32, 45, 32, 111, 99, 99, 117, 112, 97, 110, 99, 121, 58]
|
||||
#data = [130, 164, 116, 105, 109, 101, 206, 0, 4, 35, 94, 163, 109, 115, 103, 217, 55, 110, 101, 119, 32, 119, 101, 98, 115, 111, 99, 107, 101, 116, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 44, 32, 115, 116, 111, 114, 105, 110, 103, 32, 97, 116, 32, 112, 111, 115, 32, 49, 32, 45, 32, 111, 99, 99, 117, 112, 97, 110, 99, 121]
|
||||
data = [130, 164, 116, 105, 109, 101, 205, 14, 216, 163, 109, 115, 103, 217, 37, 83, 112, 105, 102, 102, 115, 32, 115, 105, 122, 101, 58, 32, 49, 50, 32, 77, 66, 44, 32, 115, 101, 116, 117, 112, 32, 116, 105, 109, 101, 32, 49, 32, 115, 101, 99]
|
||||
byte_data = b''
|
||||
for e in data:
|
||||
byte_data += e.to_bytes(1, "big")
|
||||
|
||||
#print("length", len(byte_data))
|
||||
#print(byte_data.decode(errors="ignore"))
|
||||
#print(msgpack.unpackb(byte_data))
|
||||
|
||||
# %%
|
||||
for i, e in enumerate(data):
|
||||
print(i, ":", e, bin(e), chr(e))
|
||||
# %%
|
||||
#last_msg = data[17:]
|
||||
#print("len last msg", len(last_msg))
|
||||
|
||||
# %%
|