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 hardSet from 'redux-persist/lib/stateReconciler/hardSet' | ||||||
| import { PersistGate } from 'redux-persist/integration/react' | 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
 | // Navigation
 | ||||||
| import { NavigationContainer } from '@react-navigation/native'; | import { NavigationContainer } from '@react-navigation/native'; | ||||||
| import { createStackNavigator } from '@react-navigation/stack'; | import { createStackNavigator } from '@react-navigation/stack'; | ||||||
|  | @ -32,16 +26,6 @@ import ConnectingView from './views/ConnectingView'; | ||||||
| import WifiSelectionView from './views/WifiSelectionView'; | import WifiSelectionView from './views/WifiSelectionView'; | ||||||
| import WifiPasswordView from './views/WifiPasswordView'; | 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 = { | const persistConfig = { | ||||||
|     key: 'root', |     key: 'root', | ||||||
|     storage: AsyncStorage, |     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_STATE_RESPONSE: 8, | ||||||
|     WIFI_SCAN_RESPONSE: 9, |     WIFI_SCAN_RESPONSE: 9, | ||||||
|     APP_LAYER_PING: 10, |     APP_LAYER_PING: 10, | ||||||
|  |     LOG_UPDATE: 11, | ||||||
| 
 | 
 | ||||||
|     // from frontend to device
 |     // from frontend to device
 | ||||||
|     START_SESSION: 128, |     START_SESSION: 128, | ||||||
|  | @ -25,13 +26,26 @@ const OpCodes = { | ||||||
|     WIFI_STATE_SET: 133, |     WIFI_STATE_SET: 133, | ||||||
|     WIFI_STATE_GET: 134, |     WIFI_STATE_GET: 134, | ||||||
|     WIFI_TRIGGER_SCAN: 135, |     WIFI_TRIGGER_SCAN: 135, | ||||||
|  |     LOG_STREAMING_START: 136, | ||||||
|  |     LOG_STREAMING_STOP: 137 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const HEARTBEAT_TIMEOUT = 3000; | const HEARTBEAT_TIMEOUT = 3000; | ||||||
| const PROVISIONING_IP = "192.168.42.1"; | const PROVISIONING_IP = "192.168.42.1"; | ||||||
| 
 | 
 | ||||||
| export default class SwimTrackerWebsocketConnection { | 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.swimTrackerHost = swimTrackerHost; | ||||||
| 
 | 
 | ||||||
|         this.onData = onData; |         this.onData = onData; | ||||||
|  | @ -40,14 +54,14 @@ export default class SwimTrackerWebsocketConnection { | ||||||
|         this.onWifiStateInfo = onWifiStateInfo; |         this.onWifiStateInfo = onWifiStateInfo; | ||||||
|         this.onConnect = onConnect; |         this.onConnect = onConnect; | ||||||
|         this.onDisconnect = onDisconnect; |         this.onDisconnect = onDisconnect; | ||||||
| 
 |         this.onLogMessage = () => {}; | ||||||
|          |          | ||||||
|         // try configured URL and provisioning URL
 |         // try configured URL and provisioning URL
 | ||||||
|         const urls = [`ws://${swimTrackerHost}:81`, `ws://${PROVISIONING_IP}:81`]; |         const urls = [`ws://${swimTrackerHost}:81`, `ws://${PROVISIONING_IP}:81`]; | ||||||
|         let urlIndex = 0; |         let urlIndex = 0; | ||||||
|         const urlProvider = () => urls[urlIndex++ % urls.length]; // round robin url provider
 |         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.onmessage = this._onMessage; | ||||||
|         this.ws.onopen = this.onConnect; |         this.ws.onopen = this.onConnect; | ||||||
|         this.ws.onclose = this.onDisconnect; |         this.ws.onclose = this.onDisconnect; | ||||||
|  | @ -98,6 +112,14 @@ export default class SwimTrackerWebsocketConnection { | ||||||
|         this._sendMsg(OpCodes.TARE); |         this._sendMsg(OpCodes.TARE); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     sendLogStreamStartCommand = () => { | ||||||
|  |         this._sendMsg(OpCodes.LOG_STREAMING_START); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sendLogStreamStopCommand = () => { | ||||||
|  |         this._sendMsg(OpCodes.LOG_STREAMING_STOP); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     scanWifiNetworks() { |     scanWifiNetworks() { | ||||||
|         console.log("Trigger wifi scan"); |         console.log("Trigger wifi scan"); | ||||||
|         this._sendMsg(OpCodes.WIFI_TRIGGER_SCAN); |         this._sendMsg(OpCodes.WIFI_TRIGGER_SCAN); | ||||||
|  | @ -177,6 +199,9 @@ export default class SwimTrackerWebsocketConnection { | ||||||
|         } else if (opCode === OpCodes.WIFI_STATE_RESPONSE) { |         } else if (opCode === OpCodes.WIFI_STATE_RESPONSE) { | ||||||
|             const wifiInfo = msgpack.decode(payload, { codec: this.msgpackCodec }); |             const wifiInfo = msgpack.decode(payload, { codec: this.msgpackCodec }); | ||||||
|             this.onWifiStateInfo(wifiInfo); |             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) { |         } else if (opCode === OpCodes.APP_LAYER_PING) { | ||||||
|             //console.log("got heartbeat");
 |             //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 SetupView from '../components/SetupView'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { changeSwimTrackerHostname } from '../state/Reducer'; | 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*$)/; | 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 * as msgpack from 'msgpack-lite'; | ||||||
| import { timeSince } from '../utility/TimeUtils'; | import { timeSince } from '../utility/TimeUtils'; | ||||||
| import XMLParser from 'react-xml-parser'; | import XMLParser from 'react-xml-parser'; | ||||||
| import i18n from 'i18n-js'; | import {i18n} from '../utility/i18n'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| function SessionCard(props) { | function SessionCard(props) { | ||||||
|  | @ -13,7 +13,7 @@ import MaterialCommIcon from "react-native-vector-icons/MaterialCommunityIcons"; | ||||||
| 
 | 
 | ||||||
| import EntypoIcon from "react-native-vector-icons/Entypo"; | 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 { ConnState, startSession } from '../state/DeviceReduxCoupling'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
|  | @ -8,13 +8,11 @@ import { | ||||||
|   Switch, |   Switch, | ||||||
| } from "react-native"; | } from "react-native"; | ||||||
| import themeColors from '../components/themeColors'; | 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 ImageHeader from "../components/ImageHeader"; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { TouchableOpacity } from "react-native-gesture-handler"; | import { TouchableOpacity } from "react-native-gesture-handler"; | ||||||
| import request from '../utility/PromiseRequest'; | 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_HOST = "server" | ||||||
| DEPLOY_PATH = "/volumes/swimtracker" | DEPLOY_PATH = "/volumes/swimtracker" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| APP_UPDATE_FOLDER = "app-update" | APP_UPDATE_FOLDER = "app-update" | ||||||
| APP_FOLDER = "app" | 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 | expo export --public-url https://swimtracker.bauer.tech/app-update | ||||||
| scp -r dist/* root@server:"/volumes/swimtracker/app-update/" | scp -r dist/* root@server:"/volumes/swimtracker/app-update/" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # Needs jdk 8 | # Needs jdk 8 | ||||||
| apt install openjdk-8-jdk-headless | apt install openjdk-8-jdk-headless | ||||||
| export PATH=/usr/lib/jvm/java-8-openjdk-amd64/bin/:$PATH | 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)) | ||||||
|  | 
 | ||||||
|  | # %% | ||||||