Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
eb6d77487e
commit
c081d3c86a
@ -860,6 +860,9 @@ final class RitualStore: RitualStoreProviding {
|
||||
reloadRituals()
|
||||
// Notify widgets that data has changed
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
// Trigger a UI refresh for observation-based views
|
||||
analyticsNeedsRefresh = true
|
||||
insightCardsNeedRefresh = true
|
||||
} catch {
|
||||
lastErrorMessage = error.localizedDescription
|
||||
}
|
||||
|
||||
@ -98,21 +98,18 @@ struct HistoryMonthView: View {
|
||||
private var dayGrid: some View {
|
||||
let columns = Array(repeating: GridItem(.flexible(), spacing: Design.Spacing.xSmall), count: 7)
|
||||
let dates = daysInMonth
|
||||
let progressValues = dates.map { date -> Double? in
|
||||
guard let date else { return nil }
|
||||
return date > today ? 0 : completionRate(date, selectedRitual)
|
||||
}
|
||||
|
||||
return LazyVGrid(columns: columns, spacing: Design.Spacing.xSmall) {
|
||||
ForEach(Array(dates.enumerated()), id: \.offset) { index, date in
|
||||
if let date = date {
|
||||
let isToday = calendar.isDate(date, inSameDayAs: today)
|
||||
let isFuture = date > today
|
||||
let progress = isFuture ? 0 : completionRate(date, selectedRitual)
|
||||
|
||||
// Show all days - future days show with 0 progress and are not tappable
|
||||
HistoryDayCell(
|
||||
date: date,
|
||||
progress: progressValues[index] ?? 0,
|
||||
progress: progress,
|
||||
isToday: isToday,
|
||||
isFuture: isFuture,
|
||||
onTap: { if !isFuture { onDayTapped(date) } }
|
||||
|
||||
@ -22,7 +22,6 @@ struct HistoryView: View {
|
||||
@State private var selectedDateItem: IdentifiableDate?
|
||||
@State private var monthsToShow = 2
|
||||
@State private var refreshToken = UUID()
|
||||
@State private var cachedProgressByDate: [Date: Double] = [:]
|
||||
|
||||
private let calendar = Calendar.current
|
||||
private let baseMonthsToShow = 2
|
||||
@ -93,8 +92,7 @@ struct HistoryView: View {
|
||||
month: month,
|
||||
selectedRitual: selectedRitual,
|
||||
completionRate: { date, ritual in
|
||||
let day = calendar.startOfDay(for: date)
|
||||
return cachedProgressByDate[day] ?? store.completionRate(for: day, ritual: ritual)
|
||||
store.completionRate(for: date, ritual: ritual)
|
||||
},
|
||||
onDayTapped: { date in
|
||||
selectedDateItem = IdentifiableDate(date: date)
|
||||
@ -138,20 +136,16 @@ struct HistoryView: View {
|
||||
if let selectedRitual {
|
||||
self.selectedRitual = newRituals.first { $0.id == selectedRitual.id }
|
||||
}
|
||||
refreshProgressCache()
|
||||
refreshToken = UUID()
|
||||
}
|
||||
.onChange(of: selectedRitual) { _, _ in
|
||||
refreshProgressCache()
|
||||
refreshToken = UUID()
|
||||
}
|
||||
.onChange(of: monthsToShow) { _, _ in
|
||||
refreshProgressCache()
|
||||
refreshToken = UUID()
|
||||
}
|
||||
.onAppear {
|
||||
store.refreshIfNeeded()
|
||||
refreshProgressCache()
|
||||
}
|
||||
.sheet(item: $selectedDateItem) { item in
|
||||
HistoryDayDetailSheet(
|
||||
@ -199,32 +193,6 @@ struct HistoryView: View {
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
|
||||
private func refreshProgressCache() {
|
||||
Task { @MainActor in
|
||||
await Task.yield()
|
||||
let snapshotMonths = months
|
||||
let selected = selectedRitual
|
||||
var result: [Date: Double] = [:]
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
|
||||
for month in snapshotMonths {
|
||||
guard let firstOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: month)),
|
||||
let range = calendar.range(of: .day, in: .month, for: firstOfMonth) else {
|
||||
continue
|
||||
}
|
||||
|
||||
for day in range {
|
||||
guard let date = calendar.date(byAdding: .day, value: day - 1, to: firstOfMonth) else { continue }
|
||||
let normalizedDate = calendar.startOfDay(for: date)
|
||||
guard normalizedDate <= today else { continue }
|
||||
result[normalizedDate] = store.completionRate(for: normalizedDate, ritual: selected)
|
||||
}
|
||||
}
|
||||
let cache = result
|
||||
cachedProgressByDate = cache
|
||||
}
|
||||
}
|
||||
|
||||
private func totalMonthsAvailable(from currentMonth: Date) -> Int {
|
||||
guard let earliestActivity = store.earliestActivityDate() else { return baseMonthsToShow }
|
||||
let earliestMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: earliestActivity)) ?? currentMonth
|
||||
|
||||
@ -139,8 +139,8 @@ struct AndromidaWidgetProvider: AppIntentTimelineProvider {
|
||||
// Calculate overall progress across ALL rituals for today
|
||||
let overallRate = RitualAnalytics.overallCompletionRate(on: today, from: rituals)
|
||||
|
||||
// Next habits (limit to 4) - still filtered by current time of day for the list
|
||||
let nextHabits = visibleHabits.prefix(4)
|
||||
// Next habits (limit to 3) - still filtered by current time of day for the list
|
||||
let nextHabits = visibleHabits.prefix(3)
|
||||
|
||||
// Streak calculation
|
||||
let streak = RitualAnalytics.calculateCurrentStreak(rituals: rituals)
|
||||
|
||||
@ -31,6 +31,7 @@ struct LargeWidgetView: View {
|
||||
|
||||
Divider()
|
||||
.background(AppTextColors.primary.opacity(0.2))
|
||||
.padding(.vertical, Design.Spacing.small)
|
||||
|
||||
if entry.nextHabits.isEmpty {
|
||||
WidgetEmptyStateView(
|
||||
@ -43,28 +44,32 @@ struct LargeWidgetView: View {
|
||||
isCompact: false
|
||||
)
|
||||
} else {
|
||||
Text(String(localized: "Habits"))
|
||||
.styled(.captionEmphasis, emphasis: .custom(AppTextColors.secondary))
|
||||
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
ForEach(entry.nextHabits) { habit in
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Image(systemName: habit.symbolName)
|
||||
.foregroundColor(AppAccent.primary)
|
||||
.font(.system(size: 18))
|
||||
.frame(width: 24)
|
||||
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||
Text(habit.title)
|
||||
.styled(.subheading, emphasis: .custom(AppTextColors.primary))
|
||||
Text(habit.ritualTitle)
|
||||
.styled(.caption, emphasis: .custom(AppTextColors.tertiary))
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||
Text(String(localized: "Habits"))
|
||||
.styled(.captionEmphasis, emphasis: .custom(AppTextColors.secondary))
|
||||
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
ForEach(entry.nextHabits) { habit in
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Image(systemName: habit.symbolName)
|
||||
.foregroundColor(AppAccent.primary)
|
||||
.font(.system(size: 18))
|
||||
.frame(width: 24)
|
||||
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
|
||||
Text(habit.title)
|
||||
.styled(.subheading, emphasis: .custom(AppTextColors.primary))
|
||||
.lineLimit(1)
|
||||
Text(habit.ritualTitle)
|
||||
.styled(.caption, emphasis: .custom(AppTextColors.tertiary))
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image(systemName: habit.isCompleted ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(habit.isCompleted ? .green : AppTextColors.primary.opacity(0.2))
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
Spacer()
|
||||
|
||||
Image(systemName: habit.isCompleted ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(habit.isCompleted ? .green : AppTextColors.primary.opacity(0.2))
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user