Files
GHOSTWIRE/ios/GHOSTWIRE/Logo.swift
T
Daniel Redetzke 066d669f3a Add native iOS app with App Store preparation
iOS app (ios/, SwiftUI, iOS 17+) in the web UI's styling with the Kamon
logo. It covers everything the web interface does except password, API
tokens and backups: dashboard, peers with search and filter, peer detail
with traffic charts, add/edit peers, one-time config with QR code and
share sheet, server settings with apply bar, app settings, data
retention and log viewer.

- Pairing by QR code or pasted pairing code; token kept in the keychain;
  self-signed certificates are pinned by SHA-256 fingerprint.
- Colour providers and logo drawing are nonisolated: SwiftUI's background
  renderer calls them, and main-actor closures crashed there when the
  camera scanner was open.
- App Store: version 1.0, export compliance, privacy manifest, app icon,
  release.sh (archive and upload), listing text, review notes and 6.9"
  screenshots in ios/AppStore.

Server:
- Full-access API tokens may use settings, logs and restart; password,
  tokens, backup/restore and the admin username stay admin-only.
- Web pairing dialog gains "Copy pairing code".
- The development simulator reports health checks in Linux wording.
2026-10-03 18:34:41 +03:00

69 lines
2.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
/// The Kamon mark: a crest ring around the ghost, drawn from the same
/// 64×64 geometry as favicon.svg.
struct KamonMark: View {
var size: CGFloat = 40
var body: some View {
Canvas(renderer: Self.draw)
.frame(width: size, height: size)
.accessibilityHidden(true)
}
// nonisolated: SwiftUI may render a Canvas on its background render
// thread; a main-actor-bound renderer would crash there.
nonisolated private static func draw(_ ctx: inout GraphicsContext, _ canvas: CGSize) {
let s = canvas.width / 64
let tile = CGRect(x: 0, y: 0, width: 64 * s, height: 64 * s)
ctx.fill(Path(roundedRect: tile, cornerRadius: 14 * s), with: .color(.gwSumi))
ctx.stroke(Path(roundedRect: tile.insetBy(dx: 0.5 * s, dy: 0.5 * s), cornerRadius: 13.5 * s),
with: .color(.white.opacity(0.2)), lineWidth: s)
ctx.stroke(Path(ellipseIn: CGRect(x: 10 * s, y: 10 * s, width: 44 * s, height: 44 * s)),
with: .color(.white), lineWidth: 3.5 * s)
// The ghost is scaled by 0.66 around (32, 33), as in the SVG.
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
CGPoint(x: (32 + (x - 32) * 0.66) * s, y: (33 + (y - 33) * 0.66) * s)
}
var ghost = Path()
ghost.move(to: p(18, 50))
ghost.addLine(to: p(18, 30))
ghost.addRelativeArc(center: p(32, 30), radius: 14 * 0.66 * s,
startAngle: .degrees(180), delta: .degrees(180))
ghost.addLine(to: p(46, 50))
for (x, y) in [(41.3, 46.0), (36.7, 50.0), (32.0, 46.0), (27.3, 50.0), (22.7, 46.0)] {
ghost.addLine(to: p(x, y))
}
ghost.closeSubpath()
ctx.fill(ghost, with: .color(.white))
let r = 3.2 * 0.66 * s
for c in [p(27, 30), p(37, 30)] {
ctx.fill(Path(ellipseIn: CGRect(x: c.x - r, y: c.y - r, width: 2 * r, height: 2 * r)), with: .color(.gwShu))
}
}
}
/// Mark, wordmark and katakana reading, as in the web UI.
struct Lockup: View {
var size: CGFloat = 36
var body: some View {
HStack(spacing: size * 0.3) {
KamonMark(size: size)
VStack(alignment: .leading, spacing: 1) {
Text("GHOSTWIRE")
.font(.system(size: size * 0.47, weight: .semibold, design: .monospaced))
.tracking(size * 0.03)
Text("ゴーストワイヤー")
.font(.system(size: size * 0.27))
.tracking(size * 0.08)
.foregroundStyle(Color.gwText2)
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("GHOSTWIRE")
}
}