feat(mobile): align RuView with Cognitum typography
26
.github/workflows/consumer-nlos-ci.yml
vendored
@@ -166,6 +166,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python - <<'PY'
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
from pathlib import Path
|
||||
@@ -209,13 +210,36 @@ jobs:
|
||||
"44 by 44 points",
|
||||
"under 1.5 seconds",
|
||||
"under 100 milliseconds",
|
||||
"zero runtime dependency",
|
||||
"Outfit",
|
||||
"JetBrains Mono",
|
||||
"0288734c3426ca9125ef4cb2e067ef057c09f3ce",
|
||||
"two pinned font asset packages",
|
||||
"Three.js",
|
||||
"physical iPhone validation pending",
|
||||
)
|
||||
for phrase in required_adr_contract:
|
||||
assert phrase in adr, f"missing mobile UI contract: {phrase}"
|
||||
|
||||
package = json.loads(Path("ui/mobile/package.json").read_text(encoding="utf-8"))
|
||||
assert package["dependencies"]["@expo-google-fonts/outfit"] == "0.4.3"
|
||||
assert package["dependencies"]["@expo-google-fonts/jetbrains-mono"] == "0.4.1"
|
||||
assert package["dependencies"]["expo-font"] == "~55.0.8"
|
||||
typography = Path("ui/mobile/src/theme/typography.ts").read_text(encoding="utf-8")
|
||||
assert "Outfit_700Bold" in typography
|
||||
assert "Outfit_400Regular" in typography
|
||||
assert "JetBrainsMono_500Medium" in typography
|
||||
native_fonts = (
|
||||
"Outfit-Regular.ttf",
|
||||
"Outfit-Medium.ttf",
|
||||
"Outfit-Bold.ttf",
|
||||
"JetBrainsMono-Regular.ttf",
|
||||
"JetBrainsMono-Medium.ttf",
|
||||
)
|
||||
info_plist = Path("ui/ios-nlos/App/Info.plist").read_text(encoding="utf-8")
|
||||
for font in native_fonts:
|
||||
assert Path("ui/ios-nlos/App/Fonts", font).is_file(), f"missing native font: {font}"
|
||||
assert font in info_plist, f"native font not registered: {font}"
|
||||
|
||||
flow = flow_path.read_text(encoding="utf-8")
|
||||
required_flow_contract = (
|
||||
"appId:",
|
||||
|
||||
@@ -19,22 +19,23 @@ most important state can consequently compete with setup copy, controls, and
|
||||
technical detail on a narrow phone viewport.
|
||||
|
||||
The requested direction is similar to the visual language of Cognitum Explain
|
||||
Studio: a near-black technical canvas, fine grid, restrained cyan and green
|
||||
accents, compact monospaced labels, thin luminous outlines, and an orbital or
|
||||
radar motif. This decision adopts that visual language as design provenance,
|
||||
not as a product or code dependency. No Cognitum name, logo, wordmark, copy,
|
||||
source code, private asset, authentication behavior, or investor-deck branding
|
||||
is reused. RuView keeps its own name, logo, evidence vocabulary, information
|
||||
Studio and the public `cognitum-one/website`: a near-black technical canvas,
|
||||
soft blue-black cards, restrained cyan and green accents, Outfit display and
|
||||
body typography, JetBrains Mono instrumentation, medium-weight headings, and
|
||||
12 to 16 pixel radii. This decision adopts that visual language as explicit
|
||||
design provenance. The authoritative token review is pinned to website commit
|
||||
`0288734c3426ca9125ef4cb2e067ef057c09f3ce`, specifically `src/index.css`,
|
||||
`tailwind.config.ts`, and `creative-brief.md`. No Cognitum name, logo, wordmark,
|
||||
copy, private asset, authentication behavior, or investor-deck branding is
|
||||
reused. RuView keeps its own name, logo, evidence vocabulary, information
|
||||
architecture, and privacy boundary.
|
||||
|
||||
The authenticated Studio route was not available to the automated cloud review
|
||||
session. The initial contract is therefore derived from the product owner's
|
||||
direction and the visual characteristics observable on the available hosted
|
||||
Cognitum Explain reference surface. Pixel equivalence with the authenticated
|
||||
Studio route is neither claimed nor required. A maintainer with authorized
|
||||
Studio access can later provide an approved reference screenshot and request a
|
||||
bounded visual refinement without changing the epistemic or privacy rules in
|
||||
this decision.
|
||||
The authenticated Studio route was not required for this refinement because
|
||||
the public website source defines the typography, colors, radii, borders, and
|
||||
surface treatment. Pixel equivalence with Studio is neither claimed nor
|
||||
required. A maintainer with authorized Studio access can later provide an
|
||||
approved reference screenshot and request a bounded visual refinement without
|
||||
changing the epistemic or privacy rules in this decision.
|
||||
|
||||
This is a UI-only decision. It must not change sensing, reconstruction,
|
||||
filtering, transport, credential storage, permissions, diagnostic contents,
|
||||
@@ -131,11 +132,15 @@ UNVERIFIED` only when an authenticated live attempt is active. It resolves to
|
||||
100 milliseconds over at least 30 repetitions on the same device.
|
||||
3. Continuous decorative animation must not be required for state recognition.
|
||||
It pauses when the app or page is inactive and honors reduced motion.
|
||||
4. The redesign adds no network request, remote font, remote image, analytics
|
||||
client, sensor subscription, or background timer.
|
||||
5. Screenshot rendering is deterministic: fixed viewport, fixed fixtures,
|
||||
4. The redesign adds no runtime network request, remote font, remote image,
|
||||
analytics client, sensor subscription, or background timer. Outfit and
|
||||
JetBrains Mono are bundled locally from two pinned font asset packages.
|
||||
5. The production web export contains only four selected Outfit weights and
|
||||
two selected JetBrains Mono weights. Their combined uncompressed font asset
|
||||
size is 456 KB. The native target bundles five TTF files totaling 400 KB.
|
||||
6. Screenshot rendering is deterministic: fixed viewport, fixed fixtures,
|
||||
reduced motion, local assets, and no dependency on a live endpoint.
|
||||
6. Point-cloud geometry is bounded to 288 schematic relay points plus 96
|
||||
7. Point-cloud geometry is bounded to 288 schematic relay points plus 96
|
||||
target-return points for each of at most 16 validated tracks, or 1,824 total
|
||||
points. Browser device pixel ratio is capped at 1.5. GPU resources, animation
|
||||
frames, resize observers, and pointer listeners are released on unmount.
|
||||
@@ -359,11 +364,12 @@ and 15 percent respectively.
|
||||
| Create a cross-platform generated token and component package now | 5 | 5 | 1 | 4 | 3 | 3.90 | Deferred because generation infrastructure is disproportionate to one screen and would expand the UI-only diff materially |
|
||||
| Keep the current generic card layout | 2 | 4 | 5 | 4 | 4 | 3.65 | Rejected because the evidence state remains visually subordinate and does not meet the requested design outcome |
|
||||
|
||||
The selected approach is expected to add roughly two small platform-specific
|
||||
presentation implementations and one shared behavioral contract, while adding
|
||||
zero runtime dependency. The cost is duplicated token maintenance. The control
|
||||
is a screenshot review plus identical state names and accessibility assertions
|
||||
on both platforms.
|
||||
The selected approach adds two small platform-specific presentation
|
||||
implementations, one shared behavioral contract, and two pinned font asset
|
||||
packages. The 456 KB web and 400 KB native uncompressed font costs replace
|
||||
runtime font requests with deterministic local loading. The remaining cost is
|
||||
duplicated token maintenance. The control is a screenshot review plus identical
|
||||
state names and accessibility assertions on both platforms.
|
||||
|
||||
### Point-cloud renderer alternatives
|
||||
|
||||
@@ -371,7 +377,7 @@ The same weights apply to the narrower renderer choice.
|
||||
|
||||
| Alternative | Clarity 25% | Epistemic safety 25% | Delivery cost 15% | Native accessibility 20% | Maintainability 15% | Weighted score | Decision |
|
||||
|---|---:|---:|---:|---:|---:|---:|---|
|
||||
| Three.js web plus SwiftUI Canvas native | 5 | 5 | 4 | 4 | 4 | 4.50 | Selected because it satisfies the requested Three.js view while preserving platform-native accessibility and adding zero runtime dependency |
|
||||
| Three.js web plus SwiftUI Canvas native | 5 | 5 | 4 | 4 | 4 | 4.50 | Selected because it satisfies the requested Three.js view while preserving platform-native accessibility and adding no renderer dependency |
|
||||
| Three.js inside a native WebView | 5 | 4 | 3 | 2 | 4 | 3.70 | Rejected because focus, Dynamic Type, startup, memory, and lifecycle behavior are weaker |
|
||||
| Add `expo-gl` and React Three Fiber for native Expo | 5 | 4 | 2 | 3 | 3 | 3.60 | Rejected because new native dependencies and build risk are disproportionate to a UI-only change |
|
||||
| Static SVG or Canvas cloud on every platform | 3 | 5 | 5 | 4 | 5 | 4.30 | Rejected as the sole implementation because it does not provide the requested interactive Three.js web experience |
|
||||
@@ -401,9 +407,9 @@ The same weights apply to the narrower renderer choice.
|
||||
|
||||
## Security and privacy
|
||||
|
||||
1. The redesign adds no permission, entitlement, endpoint, network request,
|
||||
storage key, cookie, analytics event, remote asset, background mode, or data
|
||||
retention path.
|
||||
1. The redesign adds no permission, entitlement, endpoint, runtime network
|
||||
request, storage key, cookie, analytics event, remote asset, background mode,
|
||||
or data retention path. Locally bundled font files are the only new assets.
|
||||
2. Existing credential rules remain unchanged: the Expo client holds an
|
||||
ephemeral credential in memory, while the approved native client uses its
|
||||
existing Keychain boundary. UI code cannot log, render, snapshot, export, or
|
||||
@@ -493,7 +499,7 @@ claim. A threshold without a completed physical run remains `TARGET`.
|
||||
| Requirement | Automated evidence | Human or physical evidence | Pass condition |
|
||||
|---|---|---|---|
|
||||
| UI-only scope | Pull-request diff allowlist and service or model tests | Reviewer checks no sensing, transport, permissions, persistence, or retention diff | Zero out-of-scope behavior changes |
|
||||
| Cognitum-inspired, RuView-owned language | Four screenshot baselines and token review | Product reviewer compares hierarchy and general visual language | Requested characteristics present; zero Cognitum branding or private assets |
|
||||
| Cognitum-inspired, RuView-owned language | Four screenshot baselines, Outfit and JetBrains Mono token assertions, pinned source review, and local font asset contract | Product reviewer compares hierarchy and general visual language | Requested characteristics present; zero Cognitum branding or private assets; no runtime font request |
|
||||
| Five honest states | Unit tests plus `nlos-evidence-state` Playwright and Maestro selectors | Reviewer verifies wording and fail-closed hierarchy | All five exact labels are representable; unknown never becomes verified |
|
||||
| Synthetic safety | Unit and E2E assertions for `nlos-synthetic-watermark` | Screenshot review of synthetic baseline | Label and watermark both visible |
|
||||
| LiDAR point-cloud UI | Deterministic generator tests, exact point budgets, Three.js canvas readiness, reduced-motion capture, and zero-return fail-closed assertion | Browser and native visual review plus physical iPhone GPU check | WebGL renders 288 schematic relay points plus 96 returns per gated track; unverified states render zero target returns; native equivalent is labeled as a projection |
|
||||
@@ -558,6 +564,9 @@ full iPhone validation.
|
||||
7. Run Swift package tests and the Xcode 26 unsigned simulator build. Confirm
|
||||
zero changes to core packages, sensing, transport, credentials, permissions,
|
||||
diagnostic schema, or retention.
|
||||
8. Assert the production web export contains only four Outfit and two JetBrains
|
||||
Mono files totaling no more than 500 KB uncompressed. Confirm the native
|
||||
target registers its five local TTF files and makes no font network request.
|
||||
|
||||
### Physical iPhone gate
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ watermark requirements, opens the deterministic Three.js LiDAR point cloud,
|
||||
verifies the reconstruction boundary, target lock, and confidence HUD, and
|
||||
writes the PNG files in this directory.
|
||||
|
||||
The visual system is pinned to `cognitum-one/website` commit
|
||||
`0288734c3426ca9125ef4cb2e067ef057c09f3ce`. Outfit supplies display and body
|
||||
roles, while JetBrains Mono supplies instrumentation and metric roles. Both
|
||||
font families are bundled locally. Screenshot generation makes no runtime font
|
||||
request.
|
||||
|
||||
The captures contain only the disconnected state, governed setup copy, and the
|
||||
built in synthetic fixture. The point cloud is generated locally from gated
|
||||
track hypotheses and schematic relay geometry. It is not a raw sensor point
|
||||
|
||||
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
@@ -52,7 +52,7 @@ struct ContentView: View {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 7) {
|
||||
Text("RUVIEW / MOBILE FIELD LAB")
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
.tracking(1.6)
|
||||
.foregroundStyle(RuViewTheme.muted)
|
||||
HStack(spacing: 7) {
|
||||
@@ -61,7 +61,7 @@ struct ContentView: View {
|
||||
.frame(width: 8, height: 8)
|
||||
.shadow(color: evidenceState.color.opacity(0.8), radius: 5)
|
||||
Text(evidenceState.label)
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
.foregroundStyle(evidenceState.color)
|
||||
.lineLimit(2)
|
||||
}
|
||||
@@ -85,13 +85,13 @@ struct ContentView: View {
|
||||
)
|
||||
)
|
||||
}
|
||||
.font(.system(size: heroFontSize, weight: .black, design: .rounded))
|
||||
.font(RuViewTypography.outfit(heroFontSize, weight: .bold))
|
||||
.tracking(-1.4)
|
||||
.minimumScaleFactor(0.72)
|
||||
.accessibilityElement(children: .combine)
|
||||
|
||||
Text("Review authenticated evidence, sensor provenance, and privacy controls in one fail closed instrument.")
|
||||
.font(.subheadline)
|
||||
.font(RuViewTypography.outfit(16))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -112,11 +112,11 @@ struct ContentView: View {
|
||||
Image(systemName: "lock.shield.fill")
|
||||
.foregroundStyle(RuViewTheme.green)
|
||||
Text("PRIVACY GUARD ACTIVE")
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
.tracking(0.7)
|
||||
Spacer()
|
||||
Text("FAIL CLOSED")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.foregroundStyle(RuViewTheme.muted)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
@@ -144,10 +144,10 @@ struct ContentView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(evidenceState.label)
|
||||
.font(.headline.bold().monospaced())
|
||||
.font(RuViewTypography.mono(16))
|
||||
.foregroundStyle(evidenceState.color)
|
||||
Text(evidenceState.detail)
|
||||
.font(.caption)
|
||||
.font(RuViewTypography.outfit(12))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
@@ -156,7 +156,7 @@ struct ContentView: View {
|
||||
.accessibilityElement(children: .combine)
|
||||
|
||||
Text(model.statusMessage)
|
||||
.font(.subheadline)
|
||||
.font(RuViewTypography.outfit(14))
|
||||
.foregroundStyle(.white)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(12)
|
||||
@@ -199,7 +199,7 @@ struct ContentView: View {
|
||||
? "The point cloud is a deterministic rendering of gated reconstruction tracks and relay geometry. It is not raw iPhone LiDAR output."
|
||||
: "Only validated, fresh tracks are shown. Uncertainty rings represent the reported position covariance."
|
||||
)
|
||||
.font(.caption)
|
||||
.font(RuViewTypography.outfit(12))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -210,7 +210,7 @@ struct ContentView: View {
|
||||
|
||||
if let watermark = model.frame?.watermark {
|
||||
Text(watermark)
|
||||
.font(.system(size: 35, weight: .black, design: .rounded))
|
||||
.font(RuViewTypography.outfit(35, weight: .bold))
|
||||
.tracking(2)
|
||||
.foregroundStyle(RuViewTheme.orange.opacity(0.48))
|
||||
.rotationEffect(.degrees(-18))
|
||||
@@ -222,7 +222,7 @@ struct ContentView: View {
|
||||
Image(systemName: "scope")
|
||||
.font(.title2)
|
||||
Text("NO DISPLAYABLE TRACKS")
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
}
|
||||
.foregroundStyle(RuViewTheme.muted)
|
||||
.padding(.horizontal, 14)
|
||||
@@ -539,11 +539,11 @@ struct ContentView: View {
|
||||
private func headerMetric(eyebrow: String, value: String, color: Color) -> some View {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(eyebrow)
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.tracking(0.8)
|
||||
.foregroundStyle(RuViewTheme.muted)
|
||||
Text(value)
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
.foregroundStyle(color)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.7)
|
||||
@@ -569,7 +569,7 @@ struct ContentView: View {
|
||||
)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(track.trackId)
|
||||
.font(.subheadline.bold().monospaced())
|
||||
.font(RuViewTypography.mono(14))
|
||||
.lineLimit(1)
|
||||
Text(String(
|
||||
format: "x %.2f y %.2f z %.2f m",
|
||||
@@ -577,13 +577,13 @@ struct ContentView: View {
|
||||
track.positionM.y,
|
||||
track.positionM.z
|
||||
))
|
||||
.font(.caption.monospacedDigit())
|
||||
.font(RuViewTypography.mono(11, medium: false))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
}
|
||||
Spacer(minLength: 4)
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Text("\(Int(track.confidence * 100))%")
|
||||
.font(.headline.monospacedDigit())
|
||||
.font(RuViewTypography.mono(17))
|
||||
badge(
|
||||
track.state.rawValue.uppercased(),
|
||||
color: track.state == .degraded ? RuViewTheme.orange : RuViewTheme.cyan
|
||||
@@ -606,13 +606,13 @@ struct ContentView: View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 12) {
|
||||
Text(title)
|
||||
.font(.subheadline)
|
||||
.font(RuViewTypography.outfit(14))
|
||||
Spacer()
|
||||
Label(
|
||||
availability.rawValue.capitalized,
|
||||
systemImage: availability == .available ? "checkmark.circle.fill" : "xmark.circle.fill"
|
||||
)
|
||||
.font(.caption.bold().monospaced())
|
||||
.font(RuViewTypography.mono(11))
|
||||
.foregroundStyle(availability == .available ? RuViewTheme.green : RuViewTheme.muted)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
@@ -631,12 +631,12 @@ struct ContentView: View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 12) {
|
||||
Text(label)
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.tracking(0.6)
|
||||
.foregroundStyle(RuViewTheme.muted)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.font(.caption.monospaced())
|
||||
.font(RuViewTypography.mono(11, medium: false))
|
||||
.foregroundStyle(.white)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
@@ -660,7 +660,7 @@ struct ContentView: View {
|
||||
Image(systemName: icon)
|
||||
.foregroundStyle(color)
|
||||
}
|
||||
.font(.caption)
|
||||
.font(RuViewTypography.outfit(12))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
.padding(12)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -675,14 +675,14 @@ struct ContentView: View {
|
||||
HStack(spacing: 5) {
|
||||
Circle().fill(color).frame(width: 6, height: 6)
|
||||
Text(label)
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.foregroundStyle(RuViewTheme.textSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
private func badge(_ text: String, color: Color) -> some View {
|
||||
Text(text)
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.72)
|
||||
.padding(.horizontal, 8)
|
||||
@@ -707,23 +707,23 @@ struct ContentView: View {
|
||||
.frame(width: 22, height: 2)
|
||||
.shadow(color: accent.opacity(0.8), radius: 4)
|
||||
Text(eyebrow)
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(10))
|
||||
.tracking(1.1)
|
||||
.foregroundStyle(accent)
|
||||
}
|
||||
Text(title)
|
||||
.font(.title3.bold())
|
||||
.font(RuViewTypography.outfit(20, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
content()
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(RuViewTheme.panel, in: RoundedRectangle(cornerRadius: 20))
|
||||
.background(RuViewTheme.panel, in: RoundedRectangle(cornerRadius: 16))
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.stroke(RuViewTheme.border, lineWidth: 1)
|
||||
}
|
||||
.shadow(color: accent.opacity(0.06), radius: 18, y: 8)
|
||||
.shadow(color: Color.black.opacity(0.5), radius: 18, y: 8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,19 +934,19 @@ private struct EvidencePresentation {
|
||||
}
|
||||
|
||||
private enum RuViewTheme {
|
||||
static let background = Color(red: 0.020, green: 0.035, blue: 0.051)
|
||||
static let panel = Color(red: 0.036, green: 0.061, blue: 0.078)
|
||||
static let panelStrong = Color(red: 0.049, green: 0.082, blue: 0.102)
|
||||
static let surface = Color(red: 0.050, green: 0.082, blue: 0.102)
|
||||
static let cyan = Color(red: 0.129, green: 0.831, blue: 0.906)
|
||||
static let green = Color(red: 0.361, green: 1.000, blue: 0.561)
|
||||
static let background = Color(red: 0.043, green: 0.055, blue: 0.075)
|
||||
static let panel = Color(red: 0.078, green: 0.094, blue: 0.122)
|
||||
static let panelStrong = Color(red: 0.094, green: 0.114, blue: 0.145)
|
||||
static let surface = Color(red: 0.113, green: 0.129, blue: 0.169)
|
||||
static let cyan = Color(red: 0.098, green: 0.831, blue: 0.902)
|
||||
static let green = Color(red: 0.149, green: 0.851, blue: 0.408)
|
||||
static let orange = Color(red: 1.000, green: 0.612, blue: 0.231)
|
||||
static let yellow = Color(red: 1.000, green: 0.827, blue: 0.318)
|
||||
static let red = Color(red: 1.000, green: 0.353, blue: 0.384)
|
||||
static let indigo = Color(red: 0.490, green: 0.584, blue: 1.000)
|
||||
static let muted = Color(red: 0.494, green: 0.596, blue: 0.651)
|
||||
static let textSecondary = Color(red: 0.665, green: 0.733, blue: 0.769)
|
||||
static let border = cyan.opacity(0.18)
|
||||
static let muted = Color(red: 0.482, green: 0.537, blue: 0.616)
|
||||
static let textSecondary = Color(red: 0.482, green: 0.537, blue: 0.616)
|
||||
static let border = Color(red: 0.153, green: 0.173, blue: 0.208)
|
||||
}
|
||||
|
||||
private struct InstrumentGrid: View {
|
||||
|
||||
26
ui/ios-nlos/App/DesignSystem.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
import SwiftUI
|
||||
|
||||
enum RuViewTypography {
|
||||
enum Weight {
|
||||
case regular
|
||||
case medium
|
||||
case bold
|
||||
}
|
||||
|
||||
static func outfit(_ size: CGFloat, weight: Weight = .regular) -> Font {
|
||||
let name: String
|
||||
switch weight {
|
||||
case .regular:
|
||||
name = "Outfit-Regular"
|
||||
case .medium:
|
||||
name = "Outfit-Medium"
|
||||
case .bold:
|
||||
name = "Outfit-Bold"
|
||||
}
|
||||
return .custom(name, size: size)
|
||||
}
|
||||
|
||||
static func mono(_ size: CGFloat, medium: Bool = true) -> Font {
|
||||
.custom(medium ? "JetBrainsMono-Medium" : "JetBrainsMono-Regular", size: size)
|
||||
}
|
||||
}
|
||||
BIN
ui/ios-nlos/App/Fonts/JetBrainsMono-Medium.ttf
Normal file
BIN
ui/ios-nlos/App/Fonts/JetBrainsMono-Regular.ttf
Normal file
93
ui/ios-nlos/App/Fonts/OFL-JetBrainsMono.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
93
ui/ios-nlos/App/Fonts/OFL-Outfit.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
ui/ios-nlos/App/Fonts/Outfit-Bold.ttf
Normal file
BIN
ui/ios-nlos/App/Fonts/Outfit-Medium.ttf
Normal file
BIN
ui/ios-nlos/App/Fonts/Outfit-Regular.ttf
Normal file
@@ -22,6 +22,14 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Outfit-Regular.ttf</string>
|
||||
<string>Outfit-Medium.ttf</string>
|
||||
<string>Outfit-Bold.ttf</string>
|
||||
<string>JetBrainsMono-Regular.ttf</string>
|
||||
<string>JetBrainsMono-Medium.ttf</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>RuView connects to an explicitly configured, authenticated NLOS processing server on your network.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
|
||||
@@ -133,7 +133,7 @@ struct TrackCanvas: View {
|
||||
)
|
||||
context.draw(
|
||||
Text("\(String(track.trackId.prefix(12)).uppercased()) \(Int((track.confidence * 100).rounded()))%")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(color),
|
||||
at: CGPoint(
|
||||
x: min(size.width - 62, centerX + radiusX + 34),
|
||||
@@ -144,19 +144,19 @@ struct TrackCanvas: View {
|
||||
|
||||
context.draw(
|
||||
Text("LIDAR POINT CLOUD / RECONSTRUCTION")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(cyan),
|
||||
at: CGPoint(x: size.width / 2, y: 18)
|
||||
)
|
||||
context.draw(
|
||||
Text("NOT RAW IPHONE LIDAR")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(orange.opacity(0.82)),
|
||||
at: CGPoint(x: size.width / 2, y: 34)
|
||||
)
|
||||
context.draw(
|
||||
Text("\(tracks.count * 72) GATED TARGET RETURNS")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(Color.white.opacity(0.64)),
|
||||
at: CGPoint(x: size.width / 2, y: size.height - 16)
|
||||
)
|
||||
@@ -206,7 +206,7 @@ struct TrackCanvas: View {
|
||||
)
|
||||
context.draw(
|
||||
Text(String(track.trackId.prefix(12)).uppercased())
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(.white),
|
||||
at: labelPoint
|
||||
)
|
||||
@@ -269,7 +269,7 @@ struct TrackCanvas: View {
|
||||
|
||||
context.draw(
|
||||
Text("RELAY ORIGIN")
|
||||
.font(.caption2.bold().monospaced())
|
||||
.font(RuViewTypography.mono(9))
|
||||
.foregroundColor(Color.white.opacity(0.5)),
|
||||
at: CGPoint(x: center.x, y: center.y + 17)
|
||||
)
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
A00000000000000000000005 /* RuViewNLOSCore in Frameworks */ = {isa = PBXBuildFile; productRef = J00000000000000000000001 /* RuViewNLOSCore */; };
|
||||
A00000000000000000000006 /* RuViewNLOSApple in Frameworks */ = {isa = PBXBuildFile; productRef = J00000000000000000000002 /* RuViewNLOSApple */; };
|
||||
A00000000000000000000007 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000007 /* PrivacyInfo.xcprivacy */; };
|
||||
A00000000000000000000008 /* DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000008 /* DesignSystem.swift */; };
|
||||
A00000000000000000000009 /* Outfit-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000009 /* Outfit-Regular.ttf */; };
|
||||
A0000000000000000000000A /* Outfit-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000A /* Outfit-Medium.ttf */; };
|
||||
A0000000000000000000000B /* Outfit-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000B /* Outfit-Bold.ttf */; };
|
||||
A0000000000000000000000C /* JetBrainsMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000C /* JetBrainsMono-Regular.ttf */; };
|
||||
A0000000000000000000000D /* JetBrainsMono-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000D /* JetBrainsMono-Medium.ttf */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -24,6 +30,12 @@
|
||||
B00000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B00000000000000000000006 /* RuViewNLOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RuViewNLOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B00000000000000000000007 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
B00000000000000000000008 /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = "<group>"; };
|
||||
B00000000000000000000009 /* Outfit-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Regular.ttf"; sourceTree = "<group>"; };
|
||||
B0000000000000000000000A /* Outfit-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Medium.ttf"; sourceTree = "<group>"; };
|
||||
B0000000000000000000000B /* Outfit-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Bold.ttf"; sourceTree = "<group>"; };
|
||||
B0000000000000000000000C /* JetBrainsMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Regular.ttf"; sourceTree = "<group>"; };
|
||||
B0000000000000000000000D /* JetBrainsMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Medium.ttf"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -54,6 +66,8 @@
|
||||
B00000000000000000000002 /* AppModel.swift */,
|
||||
B00000000000000000000003 /* ContentView.swift */,
|
||||
B00000000000000000000004 /* TrackCanvas.swift */,
|
||||
B00000000000000000000008 /* DesignSystem.swift */,
|
||||
C00000000000000000000003 /* Fonts */,
|
||||
B00000000000000000000005 /* Info.plist */,
|
||||
B00000000000000000000007 /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
@@ -68,6 +82,18 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C00000000000000000000003 /* Fonts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B00000000000000000000009 /* Outfit-Regular.ttf */,
|
||||
B0000000000000000000000A /* Outfit-Medium.ttf */,
|
||||
B0000000000000000000000B /* Outfit-Bold.ttf */,
|
||||
B0000000000000000000000C /* JetBrainsMono-Regular.ttf */,
|
||||
B0000000000000000000000D /* JetBrainsMono-Medium.ttf */,
|
||||
);
|
||||
path = Fonts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -134,6 +160,11 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A00000000000000000000007 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
A00000000000000000000009 /* Outfit-Regular.ttf in Resources */,
|
||||
A0000000000000000000000A /* Outfit-Medium.ttf in Resources */,
|
||||
A0000000000000000000000B /* Outfit-Bold.ttf in Resources */,
|
||||
A0000000000000000000000C /* JetBrainsMono-Regular.ttf in Resources */,
|
||||
A0000000000000000000000D /* JetBrainsMono-Medium.ttf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -148,6 +179,7 @@
|
||||
A00000000000000000000002 /* AppModel.swift in Sources */,
|
||||
A00000000000000000000003 /* ContentView.swift in Sources */,
|
||||
A00000000000000000000004 /* TrackCanvas.swift in Sources */,
|
||||
A00000000000000000000008 /* DesignSystem.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { useEffect } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { Outfit_400Regular } from '@expo-google-fonts/outfit/400Regular';
|
||||
import { Outfit_500Medium } from '@expo-google-fonts/outfit/500Medium';
|
||||
import { Outfit_600SemiBold } from '@expo-google-fonts/outfit/600SemiBold';
|
||||
import { Outfit_700Bold } from '@expo-google-fonts/outfit/700Bold';
|
||||
import { JetBrainsMono_400Regular } from '@expo-google-fonts/jetbrains-mono/400Regular';
|
||||
import { JetBrainsMono_500Medium } from '@expo-google-fonts/jetbrains-mono/500Medium';
|
||||
import { NavigationContainer, DarkTheme } from '@react-navigation/native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
@@ -12,6 +20,14 @@ import { useSettingsStore } from './src/stores/settingsStore';
|
||||
import { RootNavigator } from './src/navigation/RootNavigator';
|
||||
|
||||
export default function App() {
|
||||
const [fontsLoaded, fontError] = useFonts({
|
||||
Outfit_400Regular,
|
||||
Outfit_500Medium,
|
||||
Outfit_600SemiBold,
|
||||
Outfit_700Bold,
|
||||
JetBrainsMono_400Regular,
|
||||
JetBrainsMono_500Medium,
|
||||
});
|
||||
const serverUrl = useSettingsStore((state) => state.serverUrl);
|
||||
const rssiScanEnabled = useSettingsStore((state) => state.rssiScanEnabled);
|
||||
|
||||
@@ -47,15 +63,19 @@ export default function App() {
|
||||
(globalThis as { __appStartTime?: number }).__appStartTime = Date.now();
|
||||
}, []);
|
||||
|
||||
if (!fontsLoaded && !fontError) {
|
||||
return <View testID="font-load-gate" style={{ flex: 1, backgroundColor: '#0B0E13' }} />;
|
||||
}
|
||||
|
||||
const navigationTheme = {
|
||||
...DarkTheme,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
background: '#0A0E1A',
|
||||
card: '#0D1117',
|
||||
text: '#E2E8F0',
|
||||
border: '#1E293B',
|
||||
primary: '#32B8C6',
|
||||
background: '#0B0E13',
|
||||
card: '#14181F',
|
||||
text: '#E7EBEF',
|
||||
border: '#272C35',
|
||||
primary: '#19D4E6',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"userInterfaceStyle": "dark",
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
"backgroundColor": "#0B0E13"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
|
||||
15
ui/mobile/package-lock.json
generated
@@ -8,6 +8,8 @@
|
||||
"name": "mobile",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@expo-google-fonts/jetbrains-mono": "0.4.1",
|
||||
"@expo-google-fonts/outfit": "0.4.3",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-navigation/bottom-tabs": "^7.15.10",
|
||||
@@ -15,6 +17,7 @@
|
||||
"@types/three": "^0.183.1",
|
||||
"axios": "^1.15.2",
|
||||
"expo": "~55.0.4",
|
||||
"expo-font": "~55.0.8",
|
||||
"expo-status-bar": "~55.0.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
@@ -1694,6 +1697,18 @@
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo-google-fonts/jetbrains-mono": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@expo-google-fonts/jetbrains-mono/-/jetbrains-mono-0.4.1.tgz",
|
||||
"integrity": "sha512-CslACrtMOcRwoWXCO7OMEI+9w3fukWSoBtvNz46OqPoogEuuoY0tkDY1O8sFumk8t0pC6Cx0Xr95O0TOQhpkug==",
|
||||
"license": "MIT AND OFL-1.1"
|
||||
},
|
||||
"node_modules/@expo-google-fonts/outfit": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@expo-google-fonts/outfit/-/outfit-0.4.3.tgz",
|
||||
"integrity": "sha512-2uQmDVJencWLllxds6SG92E+SjxyZfvg7eZKZ5XLHggmm5AuUyQK7lzMAFOUzT6kheq2kJ7BAiubMdjKT32fJg==",
|
||||
"license": "MIT AND OFL-1.1"
|
||||
},
|
||||
"node_modules/@expo/cli": {
|
||||
"version": "55.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.35.tgz",
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"e2e:web": "playwright test --config playwright.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo-google-fonts/jetbrains-mono": "0.4.1",
|
||||
"@expo-google-fonts/outfit": "0.4.3",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-navigation/bottom-tabs": "^7.15.10",
|
||||
@@ -22,6 +24,7 @@
|
||||
"@types/three": "^0.183.1",
|
||||
"axios": "^1.15.2",
|
||||
"expo": "~55.0.4",
|
||||
"expo-font": "~55.0.8",
|
||||
"expo-status-bar": "~55.0.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react-native';
|
||||
import { createSyntheticNlosFrame } from '@/services/nlos.service';
|
||||
import { createLiveNlosFrameFixture } from '@/testUtils/nlosFixtures';
|
||||
import { ThemeProvider } from '@/theme/ThemeContext';
|
||||
import { typography } from '@/theme/typography';
|
||||
import {
|
||||
getBetaPlatformGuidance,
|
||||
NLOS_EXPLAINER_URL,
|
||||
@@ -73,6 +74,12 @@ describe('NLOSScreen', () => {
|
||||
mockNlosResult.connectLive.mockClear();
|
||||
});
|
||||
|
||||
it('uses the pinned Cognitum typography roles', () => {
|
||||
expect(typography.displayLg.fontFamily).toBe('Outfit_700Bold');
|
||||
expect(typography.bodyMd.fontFamily).toBe('Outfit_400Regular');
|
||||
expect(typography.mono.fontFamily).toBe('JetBrainsMono_500Medium');
|
||||
});
|
||||
|
||||
it('renders the RuView NLOS screen and iPhone API boundary', () => {
|
||||
const { NLOSScreen } = require('@/screens/NLOSScreen');
|
||||
render(<ThemeProvider><NLOSScreen /></ThemeProvider>);
|
||||
|
||||
@@ -87,7 +87,7 @@ export const GaugeArc = ({ value, min = 0, max, label, unit, color, colorTo, siz
|
||||
y={size / 2 - 8}
|
||||
fill="#E2E8F0"
|
||||
fontSize={Math.round(size * 0.16)}
|
||||
fontFamily="Courier New"
|
||||
fontFamily="JetBrainsMono_500Medium"
|
||||
fontWeight="700"
|
||||
textAnchor="middle"
|
||||
>
|
||||
@@ -98,7 +98,7 @@ export const GaugeArc = ({ value, min = 0, max, label, unit, color, colorTo, siz
|
||||
y={size / 2 + 18}
|
||||
fill="#94A3B8"
|
||||
fontSize={Math.round(size * 0.085)}
|
||||
fontFamily="Courier New"
|
||||
fontFamily="JetBrainsMono_500Medium"
|
||||
textAnchor="middle"
|
||||
letterSpacing="0.6"
|
||||
>
|
||||
|
||||
@@ -3,21 +3,21 @@ import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
|
||||
import { ThemedText } from './ThemedText';
|
||||
|
||||
export const instrumentColors = {
|
||||
background: '#05090D',
|
||||
panel: '#091218',
|
||||
panelRaised: '#0C171E',
|
||||
cyan: '#24D3E5',
|
||||
green: '#58F28B',
|
||||
cyanDim: 'rgba(36, 211, 229, 0.38)',
|
||||
greenDim: 'rgba(88, 242, 139, 0.36)',
|
||||
border: 'rgba(65, 204, 219, 0.22)',
|
||||
borderStrong: 'rgba(65, 204, 219, 0.42)',
|
||||
grid: 'rgba(92, 151, 162, 0.065)',
|
||||
text: '#F3F8FA',
|
||||
textSecondary: '#91A4AE',
|
||||
background: '#0B0E13',
|
||||
panel: '#14181F',
|
||||
panelRaised: '#181D25',
|
||||
cyan: '#19D4E6',
|
||||
green: '#26D968',
|
||||
cyanDim: 'rgba(25, 212, 230, 0.34)',
|
||||
greenDim: 'rgba(38, 217, 104, 0.32)',
|
||||
border: 'rgba(39, 44, 53, 0.92)',
|
||||
borderStrong: 'rgba(25, 212, 230, 0.30)',
|
||||
grid: 'rgba(25, 212, 230, 0.045)',
|
||||
text: '#E7EBEF',
|
||||
textSecondary: '#7B899D',
|
||||
warning: '#FFB65C',
|
||||
danger: '#FF6478',
|
||||
dimOverlay: 'rgba(5, 9, 13, 0.78)',
|
||||
dimOverlay: 'rgba(11, 14, 19, 0.82)',
|
||||
} as const;
|
||||
|
||||
interface InstrumentPanelProps {
|
||||
@@ -58,16 +58,15 @@ export const InstrumentPanel = ({
|
||||
accessibilityLabel,
|
||||
}: InstrumentPanelProps) => (
|
||||
<View testID={testID} accessibilityLabel={accessibilityLabel} style={[styles.panel, style]}>
|
||||
<View pointerEvents="none" style={styles.ambientGlow} />
|
||||
<View pointerEvents="none" style={styles.accentRail}>
|
||||
<View style={styles.accentRailCyan} />
|
||||
<View style={styles.accentRailGreen} />
|
||||
</View>
|
||||
<View pointerEvents="none" style={[styles.corner, styles.cornerTopLeft]} />
|
||||
<View pointerEvents="none" style={[styles.corner, styles.cornerBottomRight]} />
|
||||
{(eyebrow || accessory) && (
|
||||
<View style={styles.headingRow}>
|
||||
{eyebrow ? (
|
||||
<ThemedText preset="mono" style={styles.eyebrow}>{eyebrow}</ThemedText>
|
||||
<ThemedText preset="labelMd" style={styles.eyebrow}>{eyebrow}</ThemedText>
|
||||
) : <View />}
|
||||
{accessory}
|
||||
</View>
|
||||
@@ -100,14 +99,23 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: instrumentColors.panel,
|
||||
borderColor: instrumentColors.border,
|
||||
borderWidth: 1,
|
||||
borderRadius: 18,
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
shadowColor: instrumentColors.cyan,
|
||||
shadowColor: '#020306',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.07,
|
||||
shadowRadius: 24,
|
||||
elevation: 2,
|
||||
shadowOpacity: 0.72,
|
||||
shadowRadius: 20,
|
||||
elevation: 3,
|
||||
},
|
||||
ambientGlow: {
|
||||
position: 'absolute',
|
||||
top: -76,
|
||||
right: -54,
|
||||
width: 190,
|
||||
height: 190,
|
||||
borderRadius: 95,
|
||||
backgroundColor: 'rgba(25, 212, 230, 0.045)',
|
||||
},
|
||||
accentRail: {
|
||||
position: 'absolute',
|
||||
@@ -119,24 +127,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
accentRailCyan: { flex: 2, backgroundColor: instrumentColors.cyan },
|
||||
accentRailGreen: { flex: 1, backgroundColor: instrumentColors.green },
|
||||
corner: {
|
||||
position: 'absolute',
|
||||
width: 11,
|
||||
height: 11,
|
||||
borderColor: instrumentColors.cyanDim,
|
||||
},
|
||||
cornerTopLeft: {
|
||||
top: 7,
|
||||
left: 7,
|
||||
borderTopWidth: 1,
|
||||
borderLeftWidth: 1,
|
||||
},
|
||||
cornerBottomRight: {
|
||||
right: 7,
|
||||
bottom: 7,
|
||||
borderRightWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
headingRow: {
|
||||
minHeight: 22,
|
||||
flexDirection: 'row',
|
||||
@@ -147,9 +137,9 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
eyebrow: {
|
||||
color: instrumentColors.cyan,
|
||||
fontSize: 11,
|
||||
fontSize: 12,
|
||||
lineHeight: 16,
|
||||
letterSpacing: 1.4,
|
||||
letterSpacing: 1.15,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ export const MainTabs = () => {
|
||||
},
|
||||
tabBarIcon: ({ color, size }) => <Ionicons name={toIconName(route.name)} size={size} color={color} />,
|
||||
tabBarLabelStyle: {
|
||||
fontFamily: 'Courier New',
|
||||
fontFamily: 'JetBrainsMono_500Medium',
|
||||
textTransform: 'uppercase',
|
||||
fontSize: 10,
|
||||
},
|
||||
|
||||
@@ -542,7 +542,7 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: spacing.md,
|
||||
color: instrumentColors.text,
|
||||
backgroundColor: instrumentColors.background,
|
||||
fontFamily: 'Courier New',
|
||||
fontFamily: 'JetBrainsMono_400Regular',
|
||||
fontSize: 14,
|
||||
},
|
||||
credentialInputError: { borderColor: instrumentColors.danger },
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export const typography = {
|
||||
displayXl: { fontSize: 48, fontWeight: '700', letterSpacing: -1 },
|
||||
displayLg: { fontSize: 32, fontWeight: '700', letterSpacing: -0.5 },
|
||||
displayMd: { fontSize: 24, fontWeight: '600' },
|
||||
displayXl: { fontFamily: 'Outfit_700Bold', fontSize: 48, letterSpacing: -1.2 },
|
||||
displayLg: { fontFamily: 'Outfit_700Bold', fontSize: 32, letterSpacing: -0.7 },
|
||||
displayMd: { fontFamily: 'Outfit_600SemiBold', fontSize: 24, letterSpacing: -0.3 },
|
||||
labelLg: {
|
||||
fontFamily: 'Outfit_600SemiBold',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.5,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
labelMd: {
|
||||
fontFamily: 'Outfit_600SemiBold',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 1,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
bodyLg: { fontSize: 16, fontWeight: '400' },
|
||||
bodyMd: { fontSize: 14, fontWeight: '400' },
|
||||
bodySm: { fontSize: 12, fontWeight: '400' },
|
||||
bodyLg: { fontFamily: 'Outfit_400Regular', fontSize: 16 },
|
||||
bodyMd: { fontFamily: 'Outfit_400Regular', fontSize: 14 },
|
||||
bodySm: { fontFamily: 'Outfit_400Regular', fontSize: 12 },
|
||||
mono: {
|
||||
fontFamily: Platform.OS === 'ios' ? 'Courier New' : 'monospace',
|
||||
fontFamily: 'JetBrainsMono_500Medium',
|
||||
fontSize: 13,
|
||||
},
|
||||
};
|
||||
|
||||