import SwiftUI import UniformTypeIdentifiers struct ContentView: View { @EnvironmentObject private var appModel: AppModel @State private var isImporting = false var body: some View { ZStack { LinearGradient( colors: [Color.blue.opacity(0.25), Color.cyan.opacity(0.18), Color.mint.opacity(0.12)], startPoint: .topLeading, endPoint: .bottomTrailing ) .ignoresSafeArea() ScrollView { VStack(spacing: 16) { header card(roleSection) card(librarySection) card(playbackSection) card(connectionSection) } .padding(20) } } .fileImporter( isPresented: $isImporting, allowedContentTypes: [.mp3, .mpeg4Audio, .audio], allowsMultipleSelection: false ) { result in do { let url = try result.get().first guard let url else { return } try appModel.importTrack(url: url) } catch { appModel.statusText = "Import failed: \(error.localizedDescription)" } } } private var header: some View { VStack(spacing: 6) { Text("Run+") .font(.title2).bold() Text(appModel.statusText) .font(.footnote) .foregroundStyle(.secondary) } } private var roleSection: some View { HStack(spacing: 12) { Button("Host") { appModel.host() } .buttonStyle(.borderedProminent) Button("Join") { appModel.join() } .buttonStyle(.bordered) Button("Stop") { appModel.stop() } .buttonStyle(.bordered) } } private var librarySection: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text("Library") .font(.headline) Spacer() Button("Import MP3") { isImporting = true } } if appModel.library.isEmpty { Text("No local tracks yet.") .foregroundStyle(.secondary) } else { Picker("Track", selection: Binding(get: { appModel.selectedTrack }, set: { newValue in if let track = newValue { appModel.select(track: track) } })) { ForEach(appModel.library) { track in Text(track.displayName).tag(Optional(track)) } } .pickerStyle(.menu) } } .frame(maxWidth: .infinity, alignment: .leading) } private var playbackSection: some View { VStack(alignment: .leading, spacing: 8) { Text("Playback") .font(.headline) HStack(spacing: 12) { Button(appModel.isPlaying ? "Pause" : "Play") { appModel.togglePlay() } .buttonStyle(.borderedProminent) .disabled(appModel.selectedTrack == nil || appModel.role != .host) Button("Send Track") { appModel.sendTrackToPeers() } .buttonStyle(.bordered) .disabled(appModel.selectedTrack == nil || appModel.role != .host || !appModel.isConnected) Button("Sync Now") { appModel.syncNow() } .buttonStyle(.bordered) .disabled(appModel.role != .host) } HStack { Text("Position") Slider(value: Binding(get: { appModel.playbackPosition }, set: { newValue in appModel.seek(to: newValue) }), in: 0...max(appModel.selectedTrack?.duration ?? 1, 1)) } .disabled(appModel.selectedTrack == nil || appModel.role != .host) } .frame(maxWidth: .infinity, alignment: .leading) } private var connectionSection: some View { VStack(alignment: .leading, spacing: 8) { Text("Peers") .font(.headline) if appModel.peers.isEmpty { Text("No peers yet") .foregroundStyle(.secondary) } else { ForEach(appModel.peers) { peer in Text(peer.name) .font(.subheadline) } } } .frame(maxWidth: .infinity, alignment: .leading) } private func card(_ content: V) -> some View { content .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 16, style: .continuous) .strokeBorder(Color.white.opacity(0.15)) ) } }