Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>feat/music-streaming
parent
4a3dd23e57
commit
09be0460d4
@ -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" |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue