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