Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
9ade3b00ea
commit
2eb2abfba8
@ -21,6 +21,8 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
private var cachedDatesWithActivity: Set<Date> = []
|
private var cachedDatesWithActivity: Set<Date> = []
|
||||||
private var cachedPerfectDayIDs: Set<String> = []
|
private var cachedPerfectDayIDs: Set<String> = []
|
||||||
private var pendingReminderTask: Task<Void, Never>?
|
private var pendingReminderTask: Task<Void, Never>?
|
||||||
|
private var insightCardsNeedRefresh = true
|
||||||
|
private var cachedInsightCards: [InsightCard] = []
|
||||||
|
|
||||||
/// Reminder scheduler for time-slot based notifications
|
/// Reminder scheduler for time-slot based notifications
|
||||||
let reminderScheduler = ReminderScheduler()
|
let reminderScheduler = ReminderScheduler()
|
||||||
@ -401,86 +403,97 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func insightCards() -> [InsightCard] {
|
func insightCards() -> [InsightCard] {
|
||||||
return PerformanceLogger.measure("RitualStore.insightCards") {
|
refreshInsightCardsIfNeeded()
|
||||||
// Only count habits from active arcs for today's stats
|
return cachedInsightCards
|
||||||
let activeHabitsToday = habitsActive(on: Date())
|
}
|
||||||
let totalHabits = activeHabitsToday.count
|
|
||||||
let completedToday = activeHabitsToday.filter { isHabitCompletedToday($0) }.count
|
|
||||||
let completionRateValue = totalHabits == 0 ? 0 : Int((Double(completedToday) / Double(totalHabits)) * 100)
|
|
||||||
|
|
||||||
// Days active = unique calendar days with at least one check-in
|
|
||||||
let daysActiveCount = datesWithActivity().count
|
|
||||||
|
|
||||||
// Count rituals with active arcs
|
|
||||||
let activeRitualCount = currentRituals.count
|
|
||||||
|
|
||||||
// Build per-ritual progress breakdown
|
|
||||||
let habitsBreakdown = currentRituals.map { ritual in
|
|
||||||
let completed = ritual.habits.filter { isHabitCompletedToday($0) }.count
|
|
||||||
return BreakdownItem(
|
|
||||||
label: ritual.title,
|
|
||||||
value: "\(completed) of \(ritual.habits.count)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Streak tracking
|
|
||||||
let current = currentStreak()
|
|
||||||
let longest = longestStreak()
|
|
||||||
let streakBreakdown = [
|
|
||||||
BreakdownItem(label: String(localized: "Current streak"), value: "\(current) days"),
|
|
||||||
BreakdownItem(label: String(localized: "Longest streak"), value: "\(longest) days")
|
|
||||||
]
|
|
||||||
|
|
||||||
// Weekly trend
|
|
||||||
let trendData = weeklyTrendData()
|
|
||||||
let trendBreakdown = trendData.map { point in
|
|
||||||
BreakdownItem(label: point.label, value: "\(Int(point.value * 100))%")
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
func refreshInsightCardsIfNeeded() {
|
||||||
InsightCard(
|
guard insightCardsNeedRefresh else { return }
|
||||||
title: String(localized: "Active"),
|
cachedInsightCards = PerformanceLogger.measure("RitualStore.insightCards") {
|
||||||
value: "\(activeRitualCount)",
|
computeInsightCards()
|
||||||
caption: String(localized: "In progress now"),
|
|
||||||
explanation: String(localized: "Ritual arcs you're currently working on. Each ritual is a focused journey lasting 2-8 weeks, helping you build lasting habits through consistency."),
|
|
||||||
symbolName: "sparkles",
|
|
||||||
breakdown: currentRituals.map { BreakdownItem(label: $0.title, value: $0.theme) }
|
|
||||||
),
|
|
||||||
InsightCard(
|
|
||||||
title: String(localized: "Streak"),
|
|
||||||
value: "\(current)",
|
|
||||||
caption: String(localized: "Consecutive perfect days"),
|
|
||||||
explanation: String(localized: "Your current streak of consecutive days with 100% habit completion. Complete all your habits today to keep the streak going!"),
|
|
||||||
symbolName: "flame.fill",
|
|
||||||
breakdown: streakBreakdown
|
|
||||||
),
|
|
||||||
InsightCard(
|
|
||||||
title: String(localized: "Habits today"),
|
|
||||||
value: "\(completedToday)",
|
|
||||||
caption: String(localized: "Completed today"),
|
|
||||||
explanation: String(localized: "The number of habits you've checked off today across all your active rituals. Each check-in builds momentum toward your goals."),
|
|
||||||
symbolName: "checkmark.circle.fill",
|
|
||||||
breakdown: habitsBreakdown
|
|
||||||
),
|
|
||||||
InsightCard(
|
|
||||||
title: String(localized: "Completion"),
|
|
||||||
value: "\(completionRateValue)%",
|
|
||||||
caption: String(localized: "Today's progress"),
|
|
||||||
explanation: String(localized: "Your completion percentage for today across all rituals. The chart shows your last 7 days—this helps you spot patterns and stay consistent."),
|
|
||||||
symbolName: "chart.bar.fill",
|
|
||||||
breakdown: trendBreakdown,
|
|
||||||
trendData: trendData
|
|
||||||
),
|
|
||||||
InsightCard(
|
|
||||||
title: String(localized: "Days Active"),
|
|
||||||
value: "\(daysActiveCount)",
|
|
||||||
caption: String(localized: "Days you checked in"),
|
|
||||||
explanation: String(localized: "This counts every unique calendar day where you completed at least one habit. It's calculated by scanning all your habit check-ins across all rituals and counting the distinct days. For example, if you checked in on Monday, skipped Tuesday, then checked in Wednesday and Thursday, your Days Active would be 3."),
|
|
||||||
symbolName: "calendar",
|
|
||||||
breakdown: daysActiveBreakdown()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
insightCardsNeedRefresh = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func computeInsightCards() -> [InsightCard] {
|
||||||
|
// Only count habits from active arcs for today's stats
|
||||||
|
let activeHabitsToday = habitsActive(on: Date())
|
||||||
|
let totalHabits = activeHabitsToday.count
|
||||||
|
let completedToday = activeHabitsToday.filter { isHabitCompletedToday($0) }.count
|
||||||
|
let completionRateValue = totalHabits == 0 ? 0 : Int((Double(completedToday) / Double(totalHabits)) * 100)
|
||||||
|
|
||||||
|
// Days active = unique calendar days with at least one check-in
|
||||||
|
let daysActiveCount = datesWithActivity().count
|
||||||
|
|
||||||
|
// Count rituals with active arcs
|
||||||
|
let activeRitualCount = currentRituals.count
|
||||||
|
|
||||||
|
// Build per-ritual progress breakdown
|
||||||
|
let habitsBreakdown = currentRituals.map { ritual in
|
||||||
|
let completed = ritual.habits.filter { isHabitCompletedToday($0) }.count
|
||||||
|
return BreakdownItem(
|
||||||
|
label: ritual.title,
|
||||||
|
value: "\(completed) of \(ritual.habits.count)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streak tracking
|
||||||
|
let current = currentStreak()
|
||||||
|
let longest = longestStreak()
|
||||||
|
let streakBreakdown = [
|
||||||
|
BreakdownItem(label: String(localized: "Current streak"), value: "\(current) days"),
|
||||||
|
BreakdownItem(label: String(localized: "Longest streak"), value: "\(longest) days")
|
||||||
|
]
|
||||||
|
|
||||||
|
// Weekly trend
|
||||||
|
let trendData = weeklyTrendData()
|
||||||
|
let trendBreakdown = trendData.map { point in
|
||||||
|
BreakdownItem(label: point.label, value: "\(Int(point.value * 100))%")
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
InsightCard(
|
||||||
|
title: String(localized: "Active"),
|
||||||
|
value: "\(activeRitualCount)",
|
||||||
|
caption: String(localized: "In progress now"),
|
||||||
|
explanation: String(localized: "Ritual arcs you're currently working on. Each ritual is a focused journey lasting 2-8 weeks, helping you build lasting habits through consistency."),
|
||||||
|
symbolName: "sparkles",
|
||||||
|
breakdown: currentRituals.map { BreakdownItem(label: $0.title, value: $0.theme) }
|
||||||
|
),
|
||||||
|
InsightCard(
|
||||||
|
title: String(localized: "Streak"),
|
||||||
|
value: "\(current)",
|
||||||
|
caption: String(localized: "Consecutive perfect days"),
|
||||||
|
explanation: String(localized: "Your current streak of consecutive days with 100% habit completion. Complete all your habits today to keep the streak going!"),
|
||||||
|
symbolName: "flame.fill",
|
||||||
|
breakdown: streakBreakdown
|
||||||
|
),
|
||||||
|
InsightCard(
|
||||||
|
title: String(localized: "Habits today"),
|
||||||
|
value: "\(completedToday)",
|
||||||
|
caption: String(localized: "Completed today"),
|
||||||
|
explanation: String(localized: "The number of habits you've checked off today across all your active rituals. Each check-in builds momentum toward your goals."),
|
||||||
|
symbolName: "checkmark.circle.fill",
|
||||||
|
breakdown: habitsBreakdown
|
||||||
|
),
|
||||||
|
InsightCard(
|
||||||
|
title: String(localized: "Completion"),
|
||||||
|
value: "\(completionRateValue)%",
|
||||||
|
caption: String(localized: "Today's progress"),
|
||||||
|
explanation: String(localized: "Your completion percentage for today across all rituals. The chart shows your last 7 days—this helps you spot patterns and stay consistent."),
|
||||||
|
symbolName: "chart.bar.fill",
|
||||||
|
breakdown: trendBreakdown,
|
||||||
|
trendData: trendData
|
||||||
|
),
|
||||||
|
InsightCard(
|
||||||
|
title: String(localized: "Days Active"),
|
||||||
|
value: "\(daysActiveCount)",
|
||||||
|
caption: String(localized: "Days you checked in"),
|
||||||
|
explanation: String(localized: "This counts every unique calendar day where you completed at least one habit. It's calculated by scanning all your habit check-ins across all rituals and counting the distinct days. For example, if you checked in on Monday, skipped Tuesday, then checked in Wednesday and Thursday, your Days Active would be 3."),
|
||||||
|
symbolName: "calendar",
|
||||||
|
breakdown: daysActiveBreakdown()
|
||||||
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
func createQuickRitual() {
|
func createQuickRitual() {
|
||||||
@ -675,6 +688,7 @@ final class RitualStore: RitualStoreProviding {
|
|||||||
|
|
||||||
private func invalidateAnalyticsCache() {
|
private func invalidateAnalyticsCache() {
|
||||||
analyticsNeedsRefresh = true
|
analyticsNeedsRefresh = true
|
||||||
|
insightCardsNeedRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func computeDatesWithActivity() -> Set<Date> {
|
private func computeDatesWithActivity() -> Set<Date> {
|
||||||
|
|||||||
@ -87,9 +87,16 @@ struct HistoryMonthView: View {
|
|||||||
|
|
||||||
private var dayGrid: some View {
|
private var dayGrid: some View {
|
||||||
let columns = Array(repeating: GridItem(.flexible(), spacing: Design.Spacing.xSmall), count: 7)
|
let columns = Array(repeating: GridItem(.flexible(), spacing: Design.Spacing.xSmall), count: 7)
|
||||||
|
let dates = daysInMonth
|
||||||
|
let progressValues = PerformanceLogger.measure("HistoryMonthView.progressValues.\(monthTitle)") {
|
||||||
|
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) {
|
return LazyVGrid(columns: columns, spacing: Design.Spacing.xSmall) {
|
||||||
ForEach(Array(daysInMonth.enumerated()), id: \.offset) { index, date in
|
ForEach(Array(dates.enumerated()), id: \.offset) { index, date in
|
||||||
if let date = date {
|
if let date = date {
|
||||||
let isToday = calendar.isDate(date, inSameDayAs: today)
|
let isToday = calendar.isDate(date, inSameDayAs: today)
|
||||||
let isFuture = date > today
|
let isFuture = date > today
|
||||||
@ -97,7 +104,7 @@ struct HistoryMonthView: View {
|
|||||||
// Show all days - future days show with 0 progress and are not tappable
|
// Show all days - future days show with 0 progress and are not tappable
|
||||||
HistoryDayCell(
|
HistoryDayCell(
|
||||||
date: date,
|
date: date,
|
||||||
progress: isFuture ? 0 : completionRate(date, selectedRitual),
|
progress: progressValues[index] ?? 0,
|
||||||
isToday: isToday,
|
isToday: isToday,
|
||||||
isFuture: isFuture,
|
isFuture: isFuture,
|
||||||
onTap: { if !isFuture { onDayTapped(date) } }
|
onTap: { if !isFuture { onDayTapped(date) } }
|
||||||
|
|||||||
@ -19,43 +19,51 @@ struct HistoryView: View {
|
|||||||
@Bindable var store: RitualStore
|
@Bindable var store: RitualStore
|
||||||
@State private var selectedRitual: Ritual?
|
@State private var selectedRitual: Ritual?
|
||||||
@State private var selectedDateItem: IdentifiableDate?
|
@State private var selectedDateItem: IdentifiableDate?
|
||||||
@State private var showingExpandedHistory = false
|
@State private var monthsToShow = 2
|
||||||
@State private var refreshToken = UUID()
|
@State private var refreshToken = UUID()
|
||||||
|
@State private var cachedProgressByDate: [Date: Double] = [:]
|
||||||
|
|
||||||
private let calendar = Calendar.current
|
private let calendar = Calendar.current
|
||||||
|
private let baseMonthsToShow = 2
|
||||||
|
private let monthChunkSize = 6
|
||||||
|
|
||||||
/// Generate months based on expanded state
|
/// Generate months based on expanded state
|
||||||
/// - Collapsed: Last month + current month (2 months)
|
/// - Collapsed: Last month + current month (2 months)
|
||||||
/// - Expanded: Up to 12 months of history
|
/// - Expanded: Up to 12 months of history
|
||||||
/// Months are ordered oldest first, newest last (chronological order)
|
/// Months are ordered oldest first, newest last (chronological order)
|
||||||
private var months: [Date] {
|
private var months: [Date] {
|
||||||
let today = Date()
|
PerformanceLogger.measure("HistoryView.months") {
|
||||||
let currentMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: today)) ?? today
|
let today = Date()
|
||||||
|
let currentMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: today)) ?? today
|
||||||
// Determine how far back to go
|
|
||||||
let monthsBack = showingExpandedHistory ? 11 : 1 // 12 months or 2 months total
|
// Determine how far back to go
|
||||||
guard let startMonth = calendar.date(byAdding: .month, value: -monthsBack, to: currentMonth) else {
|
let totalAvailableMonths = totalMonthsAvailable(from: currentMonth)
|
||||||
return [currentMonth]
|
let effectiveMonthsToShow = min(monthsToShow, totalAvailableMonths)
|
||||||
|
let monthsBack = max(0, effectiveMonthsToShow - 1)
|
||||||
|
guard let startMonth = calendar.date(byAdding: .month, value: -monthsBack, to: currentMonth) else {
|
||||||
|
return [currentMonth]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build list of months in chronological order (oldest first)
|
||||||
|
var result: [Date] = []
|
||||||
|
var current = startMonth
|
||||||
|
|
||||||
|
while current <= currentMonth {
|
||||||
|
result.append(current)
|
||||||
|
current = calendar.date(byAdding: .month, value: 1, to: current) ?? current
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build list of months in chronological order (oldest first)
|
|
||||||
var result: [Date] = []
|
|
||||||
var current = startMonth
|
|
||||||
|
|
||||||
while current <= currentMonth {
|
|
||||||
result.append(current)
|
|
||||||
current = calendar.date(byAdding: .month, value: 1, to: current) ?? current
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if there's more history available beyond what's shown
|
/// Check if there's more history available beyond what's shown
|
||||||
private var hasMoreHistory: Bool {
|
private var hasMoreHistory: Bool {
|
||||||
guard let earliestActivity = store.earliestActivityDate() else { return false }
|
guard let earliestActivity = store.earliestActivityDate() else { return false }
|
||||||
let today = Date()
|
guard let oldestVisibleMonth = months.first else { return false }
|
||||||
guard let twoMonthsAgo = calendar.date(byAdding: .month, value: -1, to: today) else { return false }
|
let oldestVisibleStart = calendar.startOfDay(for: oldestVisibleMonth)
|
||||||
return earliestActivity < twoMonthsAgo
|
let earliestStart = calendar.startOfDay(for: earliestActivity)
|
||||||
|
return earliestStart < oldestVisibleStart
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -73,7 +81,8 @@ struct HistoryView: View {
|
|||||||
month: month,
|
month: month,
|
||||||
selectedRitual: selectedRitual,
|
selectedRitual: selectedRitual,
|
||||||
completionRate: { date, ritual in
|
completionRate: { date, ritual in
|
||||||
store.completionRate(for: date, ritual: ritual)
|
let day = calendar.startOfDay(for: date)
|
||||||
|
return cachedProgressByDate[day] ?? store.completionRate(for: day, ritual: ritual)
|
||||||
},
|
},
|
||||||
onDayTapped: { date in
|
onDayTapped: { date in
|
||||||
selectedDateItem = IdentifiableDate(date: date)
|
selectedDateItem = IdentifiableDate(date: date)
|
||||||
@ -93,8 +102,20 @@ struct HistoryView: View {
|
|||||||
if let selectedRitual {
|
if let selectedRitual {
|
||||||
self.selectedRitual = newRituals.first { $0.id == selectedRitual.id }
|
self.selectedRitual = newRituals.first { $0.id == selectedRitual.id }
|
||||||
}
|
}
|
||||||
|
refreshProgressCache()
|
||||||
refreshToken = UUID()
|
refreshToken = UUID()
|
||||||
}
|
}
|
||||||
|
.onChange(of: selectedRitual) { _, _ in
|
||||||
|
refreshProgressCache()
|
||||||
|
refreshToken = UUID()
|
||||||
|
}
|
||||||
|
.onChange(of: monthsToShow) { _, _ in
|
||||||
|
refreshProgressCache()
|
||||||
|
refreshToken = UUID()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
refreshProgressCache()
|
||||||
|
}
|
||||||
.sheet(item: $selectedDateItem) { item in
|
.sheet(item: $selectedDateItem) { item in
|
||||||
HistoryDayDetailSheet(
|
HistoryDayDetailSheet(
|
||||||
date: item.date,
|
date: item.date,
|
||||||
@ -116,13 +137,18 @@ struct HistoryView: View {
|
|||||||
if hasMoreHistory || showingExpandedHistory {
|
if hasMoreHistory || showingExpandedHistory {
|
||||||
Button {
|
Button {
|
||||||
withAnimation(.easeInOut(duration: Design.Animation.standard)) {
|
withAnimation(.easeInOut(duration: Design.Animation.standard)) {
|
||||||
showingExpandedHistory.toggle()
|
if monthsToShow > baseMonthsToShow {
|
||||||
|
monthsToShow = baseMonthsToShow
|
||||||
|
} else {
|
||||||
|
let totalAvailableMonths = totalMonthsAvailable(from: Date())
|
||||||
|
monthsToShow = min(monthsToShow + monthChunkSize, totalAvailableMonths)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
HStack(spacing: Design.Spacing.xSmall) {
|
||||||
Text(showingExpandedHistory ? String(localized: "Show less") : String(localized: "Show more"))
|
Text(monthsToShow > baseMonthsToShow ? String(localized: "Show less") : String(localized: "Show more"))
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
Image(systemName: showingExpandedHistory ? "chevron.up" : "chevron.down")
|
Image(systemName: monthsToShow > baseMonthsToShow ? "chevron.up" : "chevron.down")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
.foregroundStyle(AppAccent.primary)
|
.foregroundStyle(AppAccent.primary)
|
||||||
@ -168,6 +194,43 @@ struct HistoryView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func refreshProgressCache() {
|
||||||
|
Task { @MainActor in
|
||||||
|
await Task.yield()
|
||||||
|
let snapshotMonths = months
|
||||||
|
let selected = selectedRitual
|
||||||
|
let cache = PerformanceLogger.measure("HistoryView.progressCache") {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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
|
||||||
|
let comps = calendar.dateComponents([.month], from: earliestMonth, to: currentMonth)
|
||||||
|
let months = (comps.month ?? 0) + 1
|
||||||
|
return max(baseMonthsToShow, months)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
|
|||||||
@ -32,10 +32,15 @@ struct InsightsView: View {
|
|||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
))
|
))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
store.refreshAnalyticsIfNeeded()
|
Task {
|
||||||
|
await Task.yield()
|
||||||
|
store.refreshAnalyticsIfNeeded()
|
||||||
|
store.refreshInsightCardsIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: store.rituals) { _, _ in
|
.onChange(of: store.rituals) { _, _ in
|
||||||
store.refreshAnalyticsIfNeeded()
|
store.refreshAnalyticsIfNeeded()
|
||||||
|
store.refreshInsightCardsIfNeeded()
|
||||||
refreshToken = UUID()
|
refreshToken = UUID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ struct RootView: View {
|
|||||||
@Bindable var categoryStore: CategoryStore
|
@Bindable var categoryStore: CategoryStore
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@State private var selectedTab: RootTab
|
@State private var selectedTab: RootTab
|
||||||
|
@State private var analyticsPrewarmTask: Task<Void, Never>?
|
||||||
|
|
||||||
/// The available tabs in the app.
|
/// The available tabs in the app.
|
||||||
enum RootTab: Hashable {
|
enum RootTab: Hashable {
|
||||||
@ -87,6 +88,15 @@ struct RootView: View {
|
|||||||
let refreshStart = CFAbsoluteTimeGetCurrent()
|
let refreshStart = CFAbsoluteTimeGetCurrent()
|
||||||
store.refresh()
|
store.refresh()
|
||||||
PerformanceLogger.logDuration("RootView.refreshCurrentTab.store.refresh", from: refreshStart)
|
PerformanceLogger.logDuration("RootView.refreshCurrentTab.store.refresh", from: refreshStart)
|
||||||
|
analyticsPrewarmTask?.cancel()
|
||||||
|
if selectedTab != .insights {
|
||||||
|
analyticsPrewarmTask = Task { @MainActor in
|
||||||
|
try? await Task.sleep(for: .milliseconds(350))
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
store.refreshAnalyticsIfNeeded()
|
||||||
|
store.refreshInsightCardsIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
if selectedTab == .settings {
|
if selectedTab == .settings {
|
||||||
let settingsStart = CFAbsoluteTimeGetCurrent()
|
let settingsStart = CFAbsoluteTimeGetCurrent()
|
||||||
settingsStore.refresh()
|
settingsStore.refresh()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user