diff --git a/Music/Views/HomeView.swift b/Music/Views/HomeView.swift new file mode 100644 index 0000000..6252342 --- /dev/null +++ b/Music/Views/HomeView.swift @@ -0,0 +1,127 @@ +import SwiftUI +import Charts + +struct HomeView: View { + let recentTracks: [Track] + let trackCount: Int + let totalDuration: Double + let monthlyAdditions: [MonthlyCount] + let onTrackDoubleClick: (Track) -> Void + let onShowAll: () -> Void + + var body: some View { + HStack(alignment: .top, spacing: 0) { + recentlyAddedPanel + .frame(maxWidth: .infinity, maxHeight: .infinity) + + Divider() + + statsPanel + .frame(minWidth: 300, maxWidth: 300, maxHeight: .infinity) + } + } + + private var recentlyAddedPanel: some View { + VStack(alignment: .leading, spacing: 0) { + HStack { + Text("Recently Added") + .font(.title2.weight(.semibold)) + Spacer() + Button("Show All", action: onShowAll) + .buttonStyle(.plain) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 16) + .padding(.top, 12) + .padding(.bottom, 8) + + ScrollView { + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(recentTracks) { track in + VStack(alignment: .leading, spacing: 2) { + Text(track.title) + .font(.system(size: 13, weight: .medium)) + .lineLimit(1) + Text(track.artist) + .font(.system(size: 12)) + .foregroundStyle(.secondary) + .lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + .padding(.horizontal, 16) + .contentShape(Rectangle()) + .onTapGesture(count: 2) { + onTrackDoubleClick(track) + } + } + } + } + } + } + + private var statsPanel: some View { + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 12) { + Text("Library") + .font(.title2.weight(.semibold)) + + VStack(alignment: .leading, spacing: 8) { + Label( + "\(trackCount.formatted()) tracks", + systemImage: "music.note" + ) + .font(.system(size: 13)) + + Label( + Self.formatTotalDuration(totalDuration), + systemImage: "clock" + ) + .font(.system(size: 13)) + } + } + + if !monthlyAdditions.isEmpty { + VStack(alignment: .leading, spacing: 8) { + Text("Added per Month") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(.secondary) + + Chart(monthlyAdditions, id: \.month) { item in + BarMark( + x: .value("Month", item.month, unit: .month), + y: .value("Tracks", item.count) + ) + .foregroundStyle(Color.accentColor) + } + .chartXAxis { + AxisMarks(values: .stride(by: .month, count: 2)) { value in + AxisValueLabel(format: .dateTime.month(.abbreviated)) + } + } + .frame(height: 150) + } + } + + Spacer() + } + .padding(16) + } + + private static func formatTotalDuration(_ seconds: Double) -> String { + guard seconds.isFinite, seconds >= 0 else { return "0 minutes" } + let totalMinutes = Int(seconds) / 60 + let hours = totalMinutes / 60 + let days = hours / 24 + let remainingHours = hours % 24 + + if days > 0 { + return "\(days) days, \(remainingHours) hours" + } else if hours > 0 { + let remainingMinutes = totalMinutes % 60 + return "\(hours) hours, \(remainingMinutes) minutes" + } else { + return "\(totalMinutes) minutes" + } + } +}