Phase 0: scaffold

Two SPM packages (VNCCore, VNCUI) + thin iOS app target wired via
xcodegen. Builds for iPhone 17 simulator, unit tests pass.

- VNCCore: SessionState, SessionController stub, Transport protocol
  with DirectTransport (NWConnection), DiscoveryService (Bonjour on
  _rfb._tcp and _workstation._tcp), SavedConnection @Model,
  ConnectionStore, KeychainService, ClipboardBridge
- VNCUI: ConnectionListView, AddConnectionView, SessionView,
  FramebufferView/FramebufferUIView (UIKit CALayer), InputMapper,
  SettingsView; UIKit bits guarded with #if canImport(UIKit) so
  swift test runs on macOS
- App: @main VNCApp, AppStateController state machine, RootView
- RoyalVNCKit dependency pinned to main (transitive CryptoSwift
  constraint blocks tagged releases)
- xcodegen Project.yml + README + .gitignore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-04-16 19:29:47 -05:00
commit 2cff17fa0d
28 changed files with 1161 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
import Foundation
import Observation
import VNCCore
enum AppState: Equatable, Sendable {
case launching
case list
case error(AppError)
}
enum AppError: Equatable, Sendable {
case storageUnavailable(String)
}
@Observable
@MainActor
final class AppStateController {
private(set) var state: AppState = .launching
func initialize() async {
// Placeholder for Phase 1: warm storage, load recent connections, etc.
try? await Task.sleep(for: .milliseconds(150))
state = .list
}
func transition(to newState: AppState) {
guard isValidTransition(from: state, to: newState) else {
assertionFailure("Invalid transition: \(state)\(newState)")
return
}
state = newState
}
private func isValidTransition(from: AppState, to: AppState) -> Bool {
switch (from, to) {
case (.launching, .list), (.launching, .error): true
case (.list, .error), (.error, .list): true
default: false
}
}
}

View File

@@ -0,0 +1,45 @@
import SwiftUI
import VNCUI
struct RootView: View {
@Environment(AppStateController.self) private var appState
var body: some View {
ZStack {
switch appState.state {
case .launching:
LaunchView()
.transition(.opacity)
case .list:
ConnectionListView()
.transition(.opacity)
case .error(let error):
ErrorView(error: error)
.transition(.opacity)
}
}
.animation(.easeInOut(duration: 0.2), value: appState.state)
}
}
private struct LaunchView: View {
var body: some View {
VStack(spacing: 16) {
Image(systemName: "display")
.font(.system(size: 48, weight: .semibold))
.foregroundStyle(.tint)
ProgressView()
}
}
}
private struct ErrorView: View {
let error: AppError
var body: some View {
ContentUnavailableView(
"Something went wrong",
systemImage: "exclamationmark.triangle",
description: Text(String(describing: error))
)
}
}

17
Screens/App/VNCApp.swift Normal file
View File

@@ -0,0 +1,17 @@
import SwiftUI
import SwiftData
import VNCCore
@main
struct VNCApp: App {
@State private var appState = AppStateController()
var body: some Scene {
WindowGroup {
RootView()
.environment(appState)
.task { await appState.initialize() }
}
.modelContainer(for: SavedConnection.self)
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Screens</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>Discover computers on your network that you can control remotely.</string>
<key>NSBonjourServices</key>
<array>
<string>_rfb._tcp</string>
<string>_workstation._tcp</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>