New setup from scratch - all modules updated - app now in subfolder

This commit is contained in:
Martin Bauer 2023-09-29 22:04:52 +02:00
parent e28ab91935
commit 9b6bb7f126
63 changed files with 9633 additions and 44259 deletions

View File

@ -1 +0,0 @@
{}

14
.gitignore vendored
View File

@ -1,14 +0,0 @@
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
web-report/
/dist
venv
__pycache__

View File

@ -1 +0,0 @@
{}

3
SwimTracker/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules
/.expo
.directory

View File

@ -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,

33
SwimTracker/app.json Normal file
View File

@ -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"
]
}
}

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View File

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 340 KiB

View File

Before

Width:  |  Height:  |  Size: 602 KiB

After

Width:  |  Height:  |  Size: 602 KiB

View File

Before

Width:  |  Height:  |  Size: 740 KiB

After

Width:  |  Height:  |  Size: 740 KiB

View File

Before

Width:  |  Height:  |  Size: 899 KiB

After

Width:  |  Height:  |  Size: 899 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

109
SwimTracker/cli/index.js Normal file
View File

@ -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
*/

View File

@ -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");
}

44
SwimTracker/package.json Normal file
View File

@ -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
}

View File

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

View File

@ -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*$)/;

View File

@ -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) {

View File

@ -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';

View File

@ -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';
// ---------------------------------------------------------------------------------------------

9053
SwimTracker/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -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"

58
doc/environment-setup.md Normal file
View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
lifebuoy
power-plug
ruler
select-arrows

View File

@ -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",
}

View File

@ -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"
}

35454
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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
}

88
playground/FollowPaths.js Normal file
View File

@ -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>
)
}
}

139
playground/SliderDraft.js Normal file
View File

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

View File

@ -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))
# %%

8639
yarn.lock

File diff suppressed because it is too large Load Diff