DisklavierLink/DisklavierLink/Networking/PianoClient.swift
Maxence Socheleau 1a8aa6e0b3 Set up app skeleton: folder structure, networking abstraction, and UI
Establishes the foundational architecture for DisklavierLink:

Networking layer
- PianoClientProtocol defines the full async API (play, pause, stop,
  record, volume, skip, connect, fetchSongList)
- PianoClient is a stub implementation that logs calls and simulates
  short async delays; fetchSongList returns hardcoded sample songs
- PianoConnectionError carries a French-language error description for
  when the piano is unreachable (used by the simulation toggle and,
  later, by real HTTP failures)

State and persistence
- Song model (Identifiable, Codable, Hashable) with optional artist and
  duration fields, ready to be mapped from the future JSON API response
- SettingsStore persists host, port, and autoConnectOnLaunch to
  UserDefaults via @Published + didSet (replacing @AppStorage, which
  does not work in non-View ObservableObject classes)
- PianoViewModel (@MainActor ObservableObject) owns the client and
  settings, wraps every protocol call with error handling, and exposes
  isSimulatedPianoOn so the unreachable-piano path can be exercised
  without a physical device

UI
- Connection settings moved to a dedicated Settings window (Cmd+,)
  using SwiftUI's Settings scene and a grouped Form
- Auto-connect on launch option in Settings (off by default)
- Main window redesigned as a music-player layout: Now Playing card
  with gradient artwork tile and progress track placeholder, large
  circular play/pause button, sidebar song library with row selection
- Connection banner appears only when disconnected, showing the French
  error message with a shortcut to Settings
- Toolbar holds a status badge, Connect/Disconnect button, and a
  simulation power toggle (green = piano on, red = piano off) for
  testing the error path without real hardware

Project
- Source files reorganised into App/, Models/, Networking/,
  ViewModels/, Views/ subdirectories (PBXFileSystemSynchronizedRootGroup
  picks them up automatically — no project.pbxproj file-reference edits
  needed)
- com.apple.security.network.client entitlement added
- Deployment target updated to macOS 14.0
- SwiftFormat hook and /run skill added to .claude/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 12:59:10 +02:00

58 lines
2 KiB
Swift

import Foundation
struct PianoClient: PianoClientProtocol {
nonisolated init() {}
func connect(host: String, port: Int) async throws {
print("PianoClient.connect(host: \(host), port: \(port)) called")
try await Task.sleep(for: .milliseconds(300))
}
func play() async throws {
print("PianoClient.play() called")
try await Task.sleep(for: .milliseconds(150))
}
func pause() async throws {
print("PianoClient.pause() called")
try await Task.sleep(for: .milliseconds(150))
}
func stop() async throws {
print("PianoClient.stop() called")
try await Task.sleep(for: .milliseconds(150))
}
func record() async throws {
print("PianoClient.record() called")
try await Task.sleep(for: .milliseconds(150))
}
func setVolume(_ level: Double) async throws {
print("PianoClient.setVolume(\(String(format: "%.2f", level))) called")
try await Task.sleep(for: .milliseconds(100))
}
func skipForward() async throws {
print("PianoClient.skipForward() called")
try await Task.sleep(for: .milliseconds(150))
}
func skipBackward() async throws {
print("PianoClient.skipBackward() called")
try await Task.sleep(for: .milliseconds(150))
}
func fetchSongList() async throws -> [Song] {
print("PianoClient.fetchSongList() called")
try await Task.sleep(for: .milliseconds(500))
return [
Song(id: "1", title: "Moonlight Sonata", durationSeconds: 1220, artist: "Beethoven"),
Song(id: "2", title: "Für Elise", durationSeconds: 177, artist: "Beethoven"),
Song(id: "3", title: "Clair de Lune", durationSeconds: 290, artist: "Debussy"),
Song(id: "4", title: "Nocturne in E-flat major, Op. 9 No. 2", durationSeconds: 268, artist: "Chopin"),
Song(id: "5", title: "Gymnopédie No. 1", durationSeconds: 205, artist: "Satie"),
]
}
}