Build RoomScan AI starter app
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ yarn-error.*
|
|||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
|
.env
|
||||||
.env*.local
|
.env*.local
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
|
|||||||
21
App.tsx
21
App.tsx
@@ -1,20 +1,13 @@
|
|||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import { AppNavigator } from './src/navigation/AppNavigator';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<SafeAreaProvider>
|
||||||
<Text>Open up App.tsx to start working on your app!</Text>
|
<AppNavigator />
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="light" />
|
||||||
</View>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
122
README.md
Normal file
122
README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# RoomScan AI
|
||||||
|
|
||||||
|
RoomScan AI ist ein Starterprojekt fuer eine skalierbare iOS App zur Erstellung von 3D Raumscans. Die App soll Nutzer Schritt fuer Schritt durch einen Raumscan fuehren, Hinweise zu Geschwindigkeit, Richtung, Startpunkt und Fehlern anzeigen und am Ende ein 3D Raum-Modell visualisieren.
|
||||||
|
|
||||||
|
Die erste Version ist bewusst als React Native und Expo TypeScript Projekt aufgebaut. Sie nutzt nur notwendige Basis- und Navigationspakete, damit spaeter per `expo prebuild` sauber in ein natives iOS/Xcode-Projekt migriert werden kann.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- HomeScreen mit Projektziel und Scan-Ablauf
|
||||||
|
- ScanScreen mit Mock-Kameraansicht
|
||||||
|
- Instructions Overlay fuer gefuehrte Hinweise wie `Langsamer bewegen` oder `Richtung ändern`
|
||||||
|
- Ergebnis Screen mit Mock 3D Ansicht
|
||||||
|
- Stack Navigation mit TypeScript-Typen
|
||||||
|
- Vorbereitete Ordnerstruktur fuer Komponenten, Screens, Navigation, Services und Theme
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- React Native
|
||||||
|
- Expo
|
||||||
|
- TypeScript
|
||||||
|
- React Navigation
|
||||||
|
- iOS-Zielplattform mit spaeterer Xcode-Erweiterung
|
||||||
|
- Geplante native Integration: ARKit und RoomPlan
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```text
|
||||||
|
roomscan-ai/
|
||||||
|
├── App.tsx
|
||||||
|
├── app.json
|
||||||
|
├── package.json
|
||||||
|
├── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── InstructionOverlay.tsx
|
||||||
|
│ │ ├── PrimaryButton.tsx
|
||||||
|
│ │ └── ScanProgressCard.tsx
|
||||||
|
│ ├── navigation/
|
||||||
|
│ │ ├── AppNavigator.tsx
|
||||||
|
│ │ └── types.ts
|
||||||
|
│ ├── screens/
|
||||||
|
│ │ ├── HomeScreen.tsx
|
||||||
|
│ │ ├── ResultScreen.tsx
|
||||||
|
│ │ └── ScanScreen.tsx
|
||||||
|
│ ├── services/
|
||||||
|
│ │ └── scanGuidance.ts
|
||||||
|
│ └── theme/
|
||||||
|
│ └── colors.ts
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Voraussetzungen:
|
||||||
|
|
||||||
|
- Node.js
|
||||||
|
- npm
|
||||||
|
- Expo CLI ueber `npx expo`
|
||||||
|
- Fuer native iOS Builds spaeter: macOS, Xcode und Apple Developer Account
|
||||||
|
|
||||||
|
Abhaengigkeiten installieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Entwicklungsserver starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
iOS Preview starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run ios
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinweis: Auf Windows kann `npm run ios` kein lokales iOS-Simulator-Build starten. Fuer echte iOS-Entwicklung wird macOS mit Xcode benoetigt.
|
||||||
|
|
||||||
|
## Expo Prebuild und Xcode Vorbereitung
|
||||||
|
|
||||||
|
Das Projekt ist so vorbereitet, dass spaeter ein natives iOS-Projekt erzeugt werden kann:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run prebuild:ios
|
||||||
|
```
|
||||||
|
|
||||||
|
Dabei wird der Ordner `ios/` generiert. Dieser Ordner ist aktuell in `.gitignore` eingetragen, weil native Dateien erst versioniert werden sollten, wenn die RoomPlan-Integration konkret umgesetzt wird.
|
||||||
|
|
||||||
|
Wichtige iOS-Vorbereitungen in `app.json`:
|
||||||
|
|
||||||
|
- `bundleIdentifier`: `com.roomscanai.app`
|
||||||
|
- `NSCameraUsageDescription` fuer die spaetere Kamera- und RoomPlan-Nutzung
|
||||||
|
- `supportsTablet`: aktiviert fuer iPad-Kompatibilitaet
|
||||||
|
|
||||||
|
## RoomPlan Integration
|
||||||
|
|
||||||
|
RoomPlan ist eine native iOS-Technologie von Apple und wird nicht direkt durch Expo Go bereitgestellt. Die geplante Integration erfolgt spaeter ueber ein natives iOS-Modul oder eine Xcode-Erweiterung nach `expo prebuild`.
|
||||||
|
|
||||||
|
Geplanter Integrationspfad:
|
||||||
|
|
||||||
|
- Expo App als UI- und Navigationsbasis beibehalten
|
||||||
|
- iOS-Projekt per `expo prebuild --platform ios` erzeugen
|
||||||
|
- Native RoomPlan Capture Session in Swift implementieren
|
||||||
|
- Scan-Daten an React Native uebergeben
|
||||||
|
- Ergebnisdaten als 3D Modell oder strukturierte Raumdaten anzeigen
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- Mock UI fuer Scan Flow stabilisieren
|
||||||
|
- Gefuehrte Scan-Hinweise als State Machine modellieren
|
||||||
|
- Fehler- und Qualitaetsstatus fuer Scans erweitern
|
||||||
|
- Persistenz fuer Scan-Ergebnisse vorbereiten
|
||||||
|
- Native RoomPlan Integration in iOS/Xcode umsetzen
|
||||||
|
- 3D Ergebnisansicht mit echten RoomPlan-Daten anbinden
|
||||||
|
- Exportoptionen fuer Raumdaten pruefen
|
||||||
|
- TestFlight Build vorbereiten
|
||||||
|
- iOS App Store Veröffentlichung vorbereiten
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Ziel ist eine saubere, erweiterbare iOS App fuer gefuehrte 3D Raumscans mit einem klaren Pfad zur nativen ARKit- und RoomPlan-Integration und anschliessender Veröffentlichung im iOS App Store.
|
||||||
7
app.json
7
app.json
@@ -2,6 +2,7 @@
|
|||||||
"expo": {
|
"expo": {
|
||||||
"name": "roomscan-ai",
|
"name": "roomscan-ai",
|
||||||
"slug": "roomscan-ai",
|
"slug": "roomscan-ai",
|
||||||
|
"description": "Eine iOS App zur Erstellung von 3D Raumscans mit ARKit und RoomPlan.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
@@ -13,7 +14,11 @@
|
|||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true,
|
||||||
|
"bundleIdentifier": "com.roomscanai.app",
|
||||||
|
"infoPlist": {
|
||||||
|
"NSCameraUsageDescription": "RoomScan AI benoetigt die Kamera, um spaeter 3D Raumscans mit RoomPlan und ARKit zu erstellen."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
|
|||||||
309
package-lock.json
generated
309
package-lock.json
generated
@@ -8,10 +8,14 @@
|
|||||||
"name": "roomscan-ai",
|
"name": "roomscan-ai",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-navigation/native": "^7.2.2",
|
||||||
|
"@react-navigation/native-stack": "^7.14.12",
|
||||||
"expo": "~54.0.33",
|
"expo": "~54.0.33",
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5"
|
"react-native": "0.81.5",
|
||||||
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
"react-native-screens": "~4.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
@@ -2964,6 +2968,123 @@
|
|||||||
"integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==",
|
"integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-navigation/core": {
|
||||||
|
"version": "7.17.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.2.tgz",
|
||||||
|
"integrity": "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-navigation/routers": "^7.5.3",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"nanoid": "^3.3.11",
|
||||||
|
"query-string": "^7.1.3",
|
||||||
|
"react-is": "^19.1.0",
|
||||||
|
"use-latest-callback": "^0.2.4",
|
||||||
|
"use-sync-external-store": "^1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 18.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/core/node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/core/node_modules/react-is": {
|
||||||
|
"version": "19.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz",
|
||||||
|
"integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/elements": {
|
||||||
|
"version": "2.9.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.15.tgz",
|
||||||
|
"integrity": "sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color": "^4.2.3",
|
||||||
|
"use-latest-callback": "^0.2.4",
|
||||||
|
"use-sync-external-store": "^1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-native-masked-view/masked-view": ">= 0.2.0",
|
||||||
|
"@react-navigation/native": "^7.2.2",
|
||||||
|
"react": ">= 18.2.0",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-safe-area-context": ">= 4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@react-native-masked-view/masked-view": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/native": {
|
||||||
|
"version": "7.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.2.tgz",
|
||||||
|
"integrity": "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-navigation/core": "^7.17.2",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"nanoid": "^3.3.11",
|
||||||
|
"use-latest-callback": "^0.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 18.2.0",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/native-stack": {
|
||||||
|
"version": "7.14.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.12.tgz",
|
||||||
|
"integrity": "sha512-dUfpkrVeVKKV8iqXsmoUp3Rv0iH3YaB3eZwScru/FlcqAp/r3/qA6zEXkGX9hZK+/ziWAPFrf1frBSNbgOYSFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-navigation/elements": "^2.9.15",
|
||||||
|
"color": "^4.2.3",
|
||||||
|
"sf-symbols-typescript": "^2.1.0",
|
||||||
|
"warn-once": "^0.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-navigation/native": "^7.2.2",
|
||||||
|
"react": ">= 18.2.0",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-safe-area-context": ">= 4.0.0",
|
||||||
|
"react-native-screens": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/native/node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-navigation/routers": {
|
||||||
|
"version": "7.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
|
||||||
|
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.10",
|
"version": "0.27.10",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
|
||||||
@@ -3915,6 +4036,19 @@
|
|||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1",
|
||||||
|
"color-string": "^1.9.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3930,6 +4064,34 @@
|
|||||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/color-string": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "^1.0.0",
|
||||||
|
"simple-swizzle": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color/node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color/node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||||
@@ -4086,6 +4248,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decode-uri-component": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-extend": {
|
"node_modules/deep-extend": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
@@ -4907,6 +5078,12 @@
|
|||||||
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
|
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
@@ -4934,6 +5111,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||||
@@ -5279,6 +5465,12 @@
|
|||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-arrayish": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
@@ -7226,6 +7418,24 @@
|
|||||||
"qrcode-terminal": "bin/qrcode-terminal.js"
|
"qrcode-terminal": "bin/qrcode-terminal.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/query-string": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decode-uri-component": "^0.2.2",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue": {
|
"node_modules/queue": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
||||||
@@ -7278,6 +7488,18 @@
|
|||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-freeze": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@@ -7351,6 +7573,31 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-safe-area-context": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-screens": {
|
||||||
|
"version": "4.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
|
||||||
|
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-freeze": "^1.0.0",
|
||||||
|
"react-native-is-edge-to-edge": "^1.2.1",
|
||||||
|
"warn-once": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
|
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
|
||||||
"version": "0.81.5",
|
"version": "0.81.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
|
||||||
@@ -7827,6 +8074,15 @@
|
|||||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/sf-symbols-typescript": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -7877,6 +8133,15 @@
|
|||||||
"plist": "^3.0.5"
|
"plist": "^3.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-swizzle": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arrayish": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
@@ -7938,6 +8203,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sprintf-js": {
|
"node_modules/sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
@@ -8001,6 +8275,15 @@
|
|||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
@@ -8490,6 +8773,24 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-latest-callback": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@@ -8541,6 +8842,12 @@
|
|||||||
"makeerror": "1.0.12"
|
"makeerror": "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/warn-once": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wcwidth": {
|
"node_modules/wcwidth": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||||
|
|||||||
@@ -6,13 +6,18 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"prebuild:ios": "expo prebuild --platform ios"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-navigation/native": "^7.2.2",
|
||||||
|
"@react-navigation/native-stack": "^7.14.12",
|
||||||
"expo": "~54.0.33",
|
"expo": "~54.0.33",
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5"
|
"react-native": "0.81.5",
|
||||||
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
"react-native-screens": "~4.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
|||||||
54
src/components/InstructionOverlay.tsx
Normal file
54
src/components/InstructionOverlay.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type InstructionOverlayProps = {
|
||||||
|
title: string;
|
||||||
|
detail: string;
|
||||||
|
tone?: 'info' | 'warning' | 'danger';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function InstructionOverlay({ title, detail, tone = 'info' }: InstructionOverlayProps) {
|
||||||
|
const toneColor = tone === 'warning' ? colors.warning : tone === 'danger' ? colors.danger : colors.primary;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { borderColor: toneColor }]}>
|
||||||
|
<View style={[styles.indicator, { backgroundColor: toneColor }]} />
|
||||||
|
<View style={styles.textBlock}>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
<Text style={styles.detail}>{detail}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(8, 17, 31, 0.92)',
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 14,
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
indicator: {
|
||||||
|
borderRadius: 999,
|
||||||
|
height: 14,
|
||||||
|
width: 14,
|
||||||
|
},
|
||||||
|
textBlock: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 19,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
});
|
||||||
51
src/components/PrimaryButton.tsx
Normal file
51
src/components/PrimaryButton.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Pressable, StyleSheet, Text } from 'react-native';
|
||||||
|
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type PrimaryButtonProps = {
|
||||||
|
label: string;
|
||||||
|
onPress: () => void;
|
||||||
|
variant?: 'primary' | 'secondary';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PrimaryButton({ label, onPress, variant = 'primary' }: PrimaryButtonProps) {
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={onPress}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.button,
|
||||||
|
variant === 'secondary' && styles.secondary,
|
||||||
|
pressed && styles.pressed,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={[styles.label, variant === 'secondary' && styles.secondaryLabel]}>{label}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 18,
|
||||||
|
paddingHorizontal: 22,
|
||||||
|
paddingVertical: 16,
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
backgroundColor: colors.surfaceRaised,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
pressed: {
|
||||||
|
opacity: 0.82,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
color: colors.background,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
|
secondaryLabel: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
},
|
||||||
|
});
|
||||||
62
src/components/ScanProgressCard.tsx
Normal file
62
src/components/ScanProgressCard.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type ScanProgressCardProps = {
|
||||||
|
progress: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ScanProgressCard({ progress }: ScanProgressCardProps) {
|
||||||
|
return (
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={styles.label}>Scan-Fortschritt</Text>
|
||||||
|
<Text style={styles.value}>{progress}%</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.track}>
|
||||||
|
<View style={[styles.fill, { width: `${progress}%` }]} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.hint}>Halte das iPhone ruhig und bewege dich entlang der Wandkontur.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card: {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderRadius: 22,
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 18,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
backgroundColor: colors.surfaceRaised,
|
||||||
|
borderRadius: 999,
|
||||||
|
height: 10,
|
||||||
|
marginTop: 14,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
backgroundColor: colors.accent,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 19,
|
||||||
|
marginTop: 14,
|
||||||
|
},
|
||||||
|
});
|
||||||
29
src/navigation/AppNavigator.tsx
Normal file
29
src/navigation/AppNavigator.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
|
|
||||||
|
import { HomeScreen } from '../screens/HomeScreen';
|
||||||
|
import { ResultScreen } from '../screens/ResultScreen';
|
||||||
|
import { ScanScreen } from '../screens/ScanScreen';
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
import type { RootStackParamList } from './types';
|
||||||
|
|
||||||
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||||
|
|
||||||
|
export function AppNavigator() {
|
||||||
|
return (
|
||||||
|
<NavigationContainer>
|
||||||
|
<Stack.Navigator
|
||||||
|
screenOptions={{
|
||||||
|
headerStyle: { backgroundColor: colors.background },
|
||||||
|
headerTintColor: colors.textPrimary,
|
||||||
|
headerTitleStyle: { fontWeight: '700' },
|
||||||
|
contentStyle: { backgroundColor: colors.background },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'RoomScan AI' }} />
|
||||||
|
<Stack.Screen name="Scan" component={ScanScreen} options={{ title: 'Raum scannen' }} />
|
||||||
|
<Stack.Screen name="Result" component={ResultScreen} options={{ title: '3D Ergebnis' }} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
src/navigation/types.ts
Normal file
5
src/navigation/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type RootStackParamList = {
|
||||||
|
Home: undefined;
|
||||||
|
Scan: undefined;
|
||||||
|
Result: undefined;
|
||||||
|
};
|
||||||
108
src/screens/HomeScreen.tsx
Normal file
108
src/screens/HomeScreen.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||||
|
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import { PrimaryButton } from '../components/PrimaryButton';
|
||||||
|
import type { RootStackParamList } from '../navigation/types';
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
|
||||||
|
|
||||||
|
const steps = ['Startpunkt wählen', 'Langsam entlang der Wand bewegen', 'Hinweise beachten', '3D Modell prüfen'];
|
||||||
|
|
||||||
|
export function HomeScreen({ navigation }: HomeScreenProps) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safeArea} edges={['bottom']}>
|
||||||
|
<ScrollView contentContainerStyle={styles.content}>
|
||||||
|
<View style={styles.hero}>
|
||||||
|
<Text style={styles.eyebrow}>ARKit + RoomPlan vorbereitet</Text>
|
||||||
|
<Text style={styles.title}>Geführte 3D Raumscans fuer iOS</Text>
|
||||||
|
<Text style={styles.subtitle}>
|
||||||
|
RoomScan AI fuehrt Nutzer Schritt fuer Schritt durch den Scan und bereitet die App auf eine
|
||||||
|
native RoomPlan-Integration per Expo Prebuild vor.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.cardTitle}>Scan Ablauf</Text>
|
||||||
|
{steps.map((step, index) => (
|
||||||
|
<View key={step} style={styles.stepRow}>
|
||||||
|
<Text style={styles.stepNumber}>{index + 1}</Text>
|
||||||
|
<Text style={styles.stepText}>{step}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<PrimaryButton label="Mock Scan starten" onPress={() => navigation.navigate('Scan')} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safeArea: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
gap: 24,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderRadius: 28,
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
eyebrow: {
|
||||||
|
color: colors.primary,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: 34,
|
||||||
|
fontWeight: '900',
|
||||||
|
lineHeight: 39,
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 24,
|
||||||
|
marginTop: 14,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: colors.surfaceRaised,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
cardTitle: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '800',
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
stepRow: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 12,
|
||||||
|
paddingVertical: 9,
|
||||||
|
},
|
||||||
|
stepNumber: {
|
||||||
|
backgroundColor: colors.primaryDark,
|
||||||
|
borderRadius: 999,
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontWeight: '800',
|
||||||
|
overflow: 'hidden',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 6,
|
||||||
|
},
|
||||||
|
stepText: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
130
src/screens/ResultScreen.tsx
Normal file
130
src/screens/ResultScreen.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import { PrimaryButton } from '../components/PrimaryButton';
|
||||||
|
import type { RootStackParamList } from '../navigation/types';
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type ResultScreenProps = NativeStackScreenProps<RootStackParamList, 'Result'>;
|
||||||
|
|
||||||
|
export function ResultScreen({ navigation }: ResultScreenProps) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safeArea} edges={['bottom']}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.modelPreview}>
|
||||||
|
<View style={styles.floorPlan}>
|
||||||
|
<View style={styles.wallLong} />
|
||||||
|
<View style={styles.wallShort} />
|
||||||
|
<View style={styles.roomBlockLarge} />
|
||||||
|
<View style={styles.roomBlockSmall} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.previewLabel}>Mock 3D Modell</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.summaryCard}>
|
||||||
|
<Text style={styles.title}>Scan bereit zur Auswertung</Text>
|
||||||
|
<Text style={styles.body}>
|
||||||
|
Diese Ansicht ist ein Platzhalter fuer das spaetere RoomPlan-Ergebnis. Nach der nativen iOS
|
||||||
|
Integration werden hier erkannte Waende, Tueren, Fenster und Moebel angezeigt.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<PrimaryButton label="Neuen Scan starten" onPress={() => navigation.navigate('Scan')} />
|
||||||
|
<PrimaryButton label="Zur Startseite" variant="secondary" onPress={() => navigation.navigate('Home')} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safeArea: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
gap: 20,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
modelPreview: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderRadius: 30,
|
||||||
|
borderWidth: 1,
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: 360,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
floorPlan: {
|
||||||
|
borderColor: colors.primary,
|
||||||
|
borderRadius: 24,
|
||||||
|
borderWidth: 3,
|
||||||
|
height: 220,
|
||||||
|
transform: [{ rotateX: '58deg' }, { rotateZ: '-18deg' }],
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
wallLong: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 10,
|
||||||
|
height: 14,
|
||||||
|
left: 20,
|
||||||
|
position: 'absolute',
|
||||||
|
top: 72,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
wallShort: {
|
||||||
|
backgroundColor: colors.accent,
|
||||||
|
borderRadius: 10,
|
||||||
|
height: 110,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 48,
|
||||||
|
top: 54,
|
||||||
|
width: 14,
|
||||||
|
},
|
||||||
|
roomBlockLarge: {
|
||||||
|
backgroundColor: 'rgba(98, 214, 255, 0.24)',
|
||||||
|
borderRadius: 18,
|
||||||
|
bottom: 28,
|
||||||
|
height: 70,
|
||||||
|
left: 34,
|
||||||
|
position: 'absolute',
|
||||||
|
width: 88,
|
||||||
|
},
|
||||||
|
roomBlockSmall: {
|
||||||
|
backgroundColor: 'rgba(181, 240, 109, 0.28)',
|
||||||
|
borderRadius: 14,
|
||||||
|
height: 52,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 32,
|
||||||
|
top: 24,
|
||||||
|
width: 58,
|
||||||
|
},
|
||||||
|
previewLabel: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 15,
|
||||||
|
marginTop: 32,
|
||||||
|
},
|
||||||
|
summaryCard: {
|
||||||
|
backgroundColor: colors.surfaceRaised,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: 21,
|
||||||
|
fontWeight: '900',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 15,
|
||||||
|
lineHeight: 23,
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
});
|
||||||
135
src/screens/ScanScreen.tsx
Normal file
135
src/screens/ScanScreen.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import { InstructionOverlay } from '../components/InstructionOverlay';
|
||||||
|
import { PrimaryButton } from '../components/PrimaryButton';
|
||||||
|
import { ScanProgressCard } from '../components/ScanProgressCard';
|
||||||
|
import type { RootStackParamList } from '../navigation/types';
|
||||||
|
import { getGuidanceForProgress } from '../services/scanGuidance';
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
|
type ScanScreenProps = NativeStackScreenProps<RootStackParamList, 'Scan'>;
|
||||||
|
|
||||||
|
export function ScanScreen({ navigation }: ScanScreenProps) {
|
||||||
|
const [progress, setProgress] = useState(10);
|
||||||
|
const guidance = getGuidanceForProgress(progress);
|
||||||
|
|
||||||
|
// Simulates RoomPlan scan updates until native iOS capture is connected.
|
||||||
|
useEffect(() => {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
setProgress((current) => Math.min(current + 5, 92));
|
||||||
|
}, 1200);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safeArea} edges={['bottom']}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.cameraMock}>
|
||||||
|
<View style={styles.gridLineVertical} />
|
||||||
|
<View style={styles.gridLineHorizontal} />
|
||||||
|
<View style={styles.scanFrame}>
|
||||||
|
<Text style={styles.roomLabel}>Wohnzimmer</Text>
|
||||||
|
<View style={styles.cornerMarkerTop} />
|
||||||
|
<View style={styles.cornerMarkerBottom} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.overlayPosition}>
|
||||||
|
<InstructionOverlay title={guidance.title} detail={guidance.detail} tone={guidance.tone} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScanProgressCard progress={progress} />
|
||||||
|
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<PrimaryButton label="Scan abschliessen" onPress={() => navigation.navigate('Result')} />
|
||||||
|
<PrimaryButton label="Neu starten" variant="secondary" onPress={() => setProgress(10)} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safeArea: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
gap: 18,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
cameraMock: {
|
||||||
|
backgroundColor: '#0b1628',
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderRadius: 30,
|
||||||
|
borderWidth: 1,
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 380,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
gridLineVertical: {
|
||||||
|
backgroundColor: 'rgba(98, 214, 255, 0.16)',
|
||||||
|
height: '100%',
|
||||||
|
left: '50%',
|
||||||
|
position: 'absolute',
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
gridLineHorizontal: {
|
||||||
|
backgroundColor: 'rgba(98, 214, 255, 0.16)',
|
||||||
|
height: 1,
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
scanFrame: {
|
||||||
|
borderColor: colors.primary,
|
||||||
|
borderRadius: 24,
|
||||||
|
borderWidth: 2,
|
||||||
|
bottom: 70,
|
||||||
|
left: 34,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 34,
|
||||||
|
top: 70,
|
||||||
|
},
|
||||||
|
roomLabel: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 999,
|
||||||
|
color: colors.background,
|
||||||
|
fontWeight: '900',
|
||||||
|
marginTop: -16,
|
||||||
|
overflow: 'hidden',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
},
|
||||||
|
cornerMarkerTop: {
|
||||||
|
backgroundColor: colors.accent,
|
||||||
|
borderRadius: 8,
|
||||||
|
height: 16,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 20,
|
||||||
|
top: 42,
|
||||||
|
width: 16,
|
||||||
|
},
|
||||||
|
cornerMarkerBottom: {
|
||||||
|
backgroundColor: colors.warning,
|
||||||
|
borderRadius: 8,
|
||||||
|
bottom: 34,
|
||||||
|
height: 16,
|
||||||
|
left: 24,
|
||||||
|
position: 'absolute',
|
||||||
|
width: 16,
|
||||||
|
},
|
||||||
|
overlayPosition: {
|
||||||
|
bottom: 18,
|
||||||
|
left: 18,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 18,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
});
|
||||||
33
src/services/scanGuidance.ts
Normal file
33
src/services/scanGuidance.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export type ScanGuidance = {
|
||||||
|
title: string;
|
||||||
|
detail: string;
|
||||||
|
tone: 'info' | 'warning' | 'danger';
|
||||||
|
};
|
||||||
|
|
||||||
|
const guidanceSteps: ScanGuidance[] = [
|
||||||
|
{
|
||||||
|
title: 'Startpunkt setzen',
|
||||||
|
detail: 'Richte die Kamera auf eine freie Ecke und beginne dort mit dem Raumscan.',
|
||||||
|
tone: 'info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Langsamer bewegen',
|
||||||
|
detail: 'Die Kamera verliert Details. Reduziere die Geschwindigkeit fuer stabilere Messpunkte.',
|
||||||
|
tone: 'warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Richtung ändern',
|
||||||
|
detail: 'Schwenke leicht nach rechts, damit die Wandkante vollstaendig erkannt wird.',
|
||||||
|
tone: 'info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bereich erneut erfassen',
|
||||||
|
detail: 'Ein Objekt wurde unklar erkannt. Gehe einen Schritt zurueck und scanne die Zone erneut.',
|
||||||
|
tone: 'danger',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getGuidanceForProgress(progress: number) {
|
||||||
|
const index = Math.min(Math.floor(progress / 25), guidanceSteps.length - 1);
|
||||||
|
return guidanceSteps[index];
|
||||||
|
}
|
||||||
13
src/theme/colors.ts
Normal file
13
src/theme/colors.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const colors = {
|
||||||
|
background: '#08111f',
|
||||||
|
surface: '#101d31',
|
||||||
|
surfaceRaised: '#17263d',
|
||||||
|
primary: '#62d6ff',
|
||||||
|
primaryDark: '#0f8fbf',
|
||||||
|
accent: '#b5f06d',
|
||||||
|
warning: '#ffcf5a',
|
||||||
|
danger: '#ff7a7a',
|
||||||
|
textPrimary: '#f4f8ff',
|
||||||
|
textSecondary: '#a7b5c9',
|
||||||
|
border: '#263854',
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user