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:
41
Screens/App/AppStateController.swift
Normal file
41
Screens/App/AppStateController.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Screens/App/RootView.swift
Normal file
45
Screens/App/RootView.swift
Normal 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
17
Screens/App/VNCApp.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
55
Screens/Resources/Info.plist
Normal file
55
Screens/Resources/Info.plist
Normal 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>
|
||||
Reference in New Issue
Block a user