// // SessionsView.swift // OpenClimb // // Created by OpenClimb on 2025-01-17. // import Combine import SwiftUI struct SessionsView: View { @EnvironmentObject var dataManager: ClimbingDataManager @State private var showingAddSession = false var body: some View { NavigationView { VStack(spacing: 0) { // Active session banner if let activeSession = dataManager.activeSession, let gym = dataManager.gym(withId: activeSession.gymId) { VStack(spacing: 8) { ActiveSessionBanner(session: activeSession, gym: gym) .padding(.horizontal) } .padding(.top, 8) } // Sessions list if dataManager.sessions.isEmpty && dataManager.activeSession == nil { EmptySessionsView() } else { SessionsList() } } .navigationTitle("Sessions") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { if dataManager.gyms.isEmpty { EmptyView() } else if dataManager.activeSession == nil { Button("Start Session") { if dataManager.gyms.count == 1 { dataManager.startSession(gymId: dataManager.gyms.first!.id) } else { showingAddSession = true } } } } } .sheet(isPresented: $showingAddSession) { AddEditSessionView() } } } } struct ActiveSessionBanner: View { let session: ClimbSession let gym: Gym @EnvironmentObject var dataManager: ClimbingDataManager @State private var currentTime = Date() private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { NavigationLink(destination: SessionDetailView(sessionId: session.id)) { HStack { VStack(alignment: .leading, spacing: 4) { HStack { Image(systemName: "play.fill") .foregroundColor(.green) .font(.caption) Text("Active Session") .font(.headline) .fontWeight(.bold) } Text(gym.name) .font(.subheadline) .foregroundColor(.secondary) if let startTime = session.startTime { Text(formatDuration(from: startTime, to: currentTime)) .font(.caption) .foregroundColor(.secondary) } } Spacer() Button("End") { dataManager.endSession(session.id) } .buttonStyle(.borderedProminent) .controlSize(.small) } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(.green.opacity(0.1)) .stroke(.green.opacity(0.3), lineWidth: 1) ) } .buttonStyle(.plain) .onReceive(timer) { _ in currentTime = Date() } } private func formatDuration(from start: Date, to end: Date) -> String { let interval = end.timeIntervalSince(start) let hours = Int(interval) / 3600 let minutes = Int(interval) % 3600 / 60 let seconds = Int(interval) % 60 if hours > 0 { return String(format: "%dh %dm %ds", hours, minutes, seconds) } else if minutes > 0 { return String(format: "%dm %ds", minutes, seconds) } else { return String(format: "%ds", seconds) } } } struct SessionsList: View { @EnvironmentObject var dataManager: ClimbingDataManager private var completedSessions: [ClimbSession] { dataManager.sessions .filter { $0.status == .completed } .sorted { $0.date > $1.date } } var body: some View { List(completedSessions) { session in NavigationLink(destination: SessionDetailView(sessionId: session.id)) { SessionRow(session: session) } } .listStyle(.plain) } } struct SessionRow: View { let session: ClimbSession @EnvironmentObject var dataManager: ClimbingDataManager private var gym: Gym? { dataManager.gym(withId: session.gymId) } var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text(gym?.name ?? "Unknown Gym") .font(.headline) .fontWeight(.semibold) Spacer() Text(formatDate(session.date)) .font(.caption) .foregroundColor(.secondary) } if let duration = session.duration { Text("Duration: \(duration) minutes") .font(.subheadline) .foregroundColor(.secondary) } if let notes = session.notes, !notes.isEmpty { Text(notes) .font(.caption) .foregroundColor(.secondary) .lineLimit(2) } } .padding(.vertical, 4) } private func formatDate(_ date: Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .medium return formatter.string(from: date) } } struct EmptySessionsView: View { @EnvironmentObject var dataManager: ClimbingDataManager @State private var showingAddSession = false var body: some View { VStack(spacing: 20) { Spacer() Image(systemName: "figure.climbing") .font(.system(size: 60)) .foregroundColor(.secondary) VStack(spacing: 8) { Text(dataManager.gyms.isEmpty ? "No Gyms Available" : "No Sessions Yet") .font(.title2) .fontWeight(.bold) Text( dataManager.gyms.isEmpty ? "Add a gym first to start tracking your climbing sessions!" : "Start your first climbing session!" ) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal) } if !dataManager.gyms.isEmpty { Button("Start Session") { if dataManager.gyms.count == 1 { dataManager.startSession(gymId: dataManager.gyms.first!.id) } else { showingAddSession = true } } .buttonStyle(.borderedProminent) .controlSize(.large) } Spacer() } .sheet(isPresented: $showingAddSession) { AddEditSessionView() } } } #Preview { SessionsView() .environmentObject(ClimbingDataManager.preview) }