Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
},
"sweetpad.xcodebuildserver.serverEnv": {
"DEVELOPER_DIR": "/Applications/Xcode.app/Contents/Developer"
}
},
"diffEditor.ignoreTrimWhitespace": true
}
1 change: 1 addition & 0 deletions Bedtime/Bedtime/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ import Foundation

class Constants {
static let iconWidth: CGFloat = 30
static let sleepHistoryDays = 30
}
6 changes: 5 additions & 1 deletion Bedtime/Bedtime/Models/HealthKitManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ class HealthKitManager: ObservableObject {
private func fetchSleepDataForDisplay() async throws {
let calendar = Calendar.current
let endDate = Date()
guard let startDate = calendar.date(byAdding: .day, value: -30, to: endDate) else {
let today = calendar.startOfDay(for: endDate)
// Fetch one extra day before the UI range: grouping (midpoint + 6h) can assign
// sessions that start the previous evening to the oldest displayed day, including
// short blocks (e.g. 9–11pm) as well as overnight sleep.
guard let startDate = calendar.date(byAdding: .day, value: -Constants.sleepHistoryDays, to: today) else {
Comment thread
gsbernstein marked this conversation as resolved.
throw NSError(domain: "HealthKitManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date"])
}

Expand Down
53 changes: 34 additions & 19 deletions Bedtime/Bedtime/Views/Components/SleepDayGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ struct SleepDayGroup: View {
return (abs(difference), difference >= 0, color)
}

private var hasSessions: Bool {
!sessions.isEmpty
}

var body: some View {
VStack(spacing: 0) {
// Day header (always visible)
Expand All @@ -43,41 +47,52 @@ struct SleepDayGroup: View {
.font(.subheadline)
.fontWeight(.medium)

Text("\(timeFormatter.string(from: sessions.last?.startDate ?? Date())) - \(timeFormatter.string(from: sessions.first?.endDate ?? Date()))")
.font(.caption)
.foregroundColor(.secondary)
if hasSessions {
Text("\(timeFormatter.string(from: sessions.last!.startDate)) - \(timeFormatter.string(from: sessions.first!.endDate))")
.font(.caption)
.foregroundColor(.secondary)
Comment thread
gsbernstein marked this conversation as resolved.
} else {
Text("No sleep data")
.font(.caption)
.foregroundColor(.secondary)
}
}

Spacer()

VStack(alignment: .trailing, spacing: 2) {
Text(TimeFormatter.formatDuration(sessions.reduce(0) { $0 + $1.duration }))
.font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.primary)

HStack(spacing: 2) {
Text(balanceImpact.isPositive ? "+" : "-")
.font(.caption)
.foregroundColor(balanceImpact.color)
if hasSessions {
Text(TimeFormatter.formatDuration(sessions.reduce(0) { $0 + $1.duration }))
.font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.primary)

Text(String(format: "%.1fh", balanceImpact.value))
.font(.caption)
.foregroundColor(balanceImpact.color)
HStack(spacing: 2) {
Text(balanceImpact.isPositive ? "+" : "-")
.font(.caption)
.foregroundColor(balanceImpact.color)

Text(String(format: "%.1fh", balanceImpact.value))
.font(.caption)
.foregroundColor(balanceImpact.color)
}
}
Comment thread
gsbernstein marked this conversation as resolved.
}
Comment thread
gsbernstein marked this conversation as resolved.

Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.font(.caption)
.foregroundColor(.secondary)
if hasSessions {
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding(.vertical, 8)
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
.disabled(!hasSessions)

// Session details (expandable)
if isExpanded {
if isExpanded && hasSessions {
VStack(spacing: 4) {
ForEach(Array(sessions.enumerated()), id: \.offset) { index, session in
SleepSessionRow(session: session)
Expand Down
11 changes: 9 additions & 2 deletions Bedtime/Bedtime/Views/RecentSleepSessionsCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import SwiftUI

struct RecentSleepSessionsCard: View {

init(sessions: [Date: [SleepSession]], sleepGoal: Double) {
init(sessions: [Date: [SleepSession]], sleepGoal: Double, dayCount: Int = Constants.sleepHistoryDays) {
self.sessions = sessions
self.sortedSessions = sessions.sorted { $0.key > $1.key }
self.sleepGoal = sleepGoal

let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
self.sortedSessions = (0..<dayCount).compactMap { offset -> (Date, [SleepSession])? in
guard let day = calendar.date(byAdding: .day, value: -offset, to: today) else { return nil }
let dayStart = calendar.startOfDay(for: day)
return (dayStart, sessions[dayStart] ?? [])
}
Comment thread
gsbernstein marked this conversation as resolved.
}

let sessions: [Date: [SleepSession]]
Expand Down