import SwiftUI struct Session: Identifiable { let id = UUID() var name: String var notes: String var created: Date var measurements: [SessionMeasurement] } struct SessionMeasurement: Identifiable { let id = UUID() var type: Tab var timestamp: Date var pointCount: Int var summary: String } @Observable final class SessionStore { var sessions: [Session] = [] var selectedSession: Session? func createSession(name: String, notes: String) { let session = Session( name: name, notes: notes, created: Date(), measurements: [] ) sessions.insert(session, at: 0) selectedSession = session } func deleteSession(_ session: Session) { sessions.removeAll { $0.id == session.id } if selectedSession?.id == session.id { selectedSession = nil } } } struct SessionView: View { @State var store = SessionStore() @State private var showingNewSession = false @State private var newName = "" @State private var newNotes = "" var body: some View { GeometryReader { geo in if geo.size.width > 700 { wideLayout } else { compactLayout } } .sheet(isPresented: $showingNewSession) { newSessionSheet } } // MARK: - Wide layout (iPad) private var wideLayout: some View { HStack(spacing: 0) { sessionList .frame(width: 300) Divider() if let session = store.selectedSession { sessionDetail(session) } else { Text("Select or create a session") .foregroundStyle(.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } // MARK: - Compact layout (iPhone) private var compactLayout: some View { NavigationStack { sessionList .navigationTitle("Sessions") .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { showingNewSession = true }) { Image(systemName: "plus") } } } } } // MARK: - Session list private var sessionList: some View { VStack(spacing: 0) { HStack { Text("Sessions") .font(.headline) Spacer() Button(action: { showingNewSession = true }) { Image(systemName: "plus.circle.fill") .imageScale(.large) } } .padding() if store.sessions.isEmpty { VStack(spacing: 8) { Text("No sessions") .foregroundStyle(.secondary) Text("Create a session to organize measurements") .font(.caption) .foregroundStyle(Color(white: 0.4)) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { List(selection: Binding( get: { store.selectedSession?.id }, set: { id in store.selectedSession = store.sessions.first { $0.id == id } } )) { ForEach(store.sessions) { session in sessionRow(session) .tag(session.id) } .onDelete { indices in for idx in indices { store.deleteSession(store.sessions[idx]) } } } .listStyle(.plain) } } } private func sessionRow(_ session: Session) -> some View { VStack(alignment: .leading, spacing: 4) { Text(session.name) .font(.subheadline.weight(.medium)) HStack { Text(session.created, style: .date) Text(session.created, style: .time) } .font(.caption) .foregroundStyle(.secondary) if !session.notes.isEmpty { Text(session.notes) .font(.caption) .foregroundStyle(.tertiary) .lineLimit(1) } if !session.measurements.isEmpty { Text("\(session.measurements.count) measurement\(session.measurements.count == 1 ? "" : "s")") .font(.caption2) .foregroundStyle(.secondary) } } .padding(.vertical, 4) } // MARK: - Session detail private func sessionDetail(_ session: Session) -> some View { VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 4) { Text(session.name) .font(.title2.bold()) HStack { Text(session.created, style: .date) Text(session.created, style: .time) } .font(.subheadline) .foregroundStyle(.secondary) if !session.notes.isEmpty { Text(session.notes) .font(.body) .foregroundStyle(.secondary) } } .padding() Divider() if session.measurements.isEmpty { VStack(spacing: 8) { Text("No measurements in this session") .foregroundStyle(.secondary) Text("Run a measurement to add data") .font(.caption) .foregroundStyle(Color(white: 0.4)) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { List(session.measurements) { meas in HStack { Label(meas.type.rawValue, systemImage: measurementIcon(meas.type)) Spacer() VStack(alignment: .trailing) { Text("\(meas.pointCount) pts") .font(.caption.monospacedDigit()) Text(meas.timestamp, style: .time) .font(.caption2) .foregroundStyle(.secondary) } } } .listStyle(.plain) } } } // MARK: - New session sheet private var newSessionSheet: some View { NavigationStack { Form { Section("Session Info") { TextField("Name", text: $newName) TextField("Notes (optional)", text: $newNotes, axis: .vertical) .lineLimit(3...6) } } .navigationTitle("New Session") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { showingNewSession = false newName = "" newNotes = "" } } ToolbarItem(placement: .confirmationAction) { Button("Create") { store.createSession(name: newName, notes: newNotes) showingNewSession = false newName = "" newNotes = "" } .disabled(newName.trimmingCharacters(in: .whitespaces).isEmpty) } } } .presentationDetents([.medium]) } private func measurementIcon(_ tab: Tab) -> String { switch tab { case .eis: "waveform.path.ecg" case .lsv: "chart.xyaxis.line" case .amp: "bolt.fill" case .chlorine: "drop.fill" case .ph: "scalemass" case .sessions: "folder" } } }