Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
2eb2abfba8
commit
dd89905b29
@ -3,7 +3,9 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array/>
|
||||
<array>
|
||||
<string>iCloud.com.mbrucedogs.Andromida</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudKit</string>
|
||||
|
||||
@ -16,7 +16,11 @@ struct AndromidaApp: App {
|
||||
init() {
|
||||
// Include all models in schema - Ritual, RitualArc, and ArcHabit
|
||||
let schema = Schema([Ritual.self, RitualArc.self, ArcHabit.self])
|
||||
let configuration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
|
||||
let configuration = ModelConfiguration(
|
||||
schema: schema,
|
||||
isStoredInMemoryOnly: false,
|
||||
cloudKitDatabase: .private("iCloud.com.mbrucedogs.Andromida")
|
||||
)
|
||||
let container: ModelContainer
|
||||
do {
|
||||
container = try ModelContainer(for: schema, configurations: [configuration])
|
||||
|
||||
@ -6,11 +6,11 @@ import SwiftData
|
||||
/// while preserving historical data.
|
||||
@Model
|
||||
final class ArcHabit {
|
||||
var id: UUID
|
||||
var title: String
|
||||
var symbolName: String
|
||||
var goal: String
|
||||
var completedDayIDs: [String]
|
||||
var id: UUID = UUID()
|
||||
var title: String = ""
|
||||
var symbolName: String = ""
|
||||
var goal: String = ""
|
||||
var completedDayIDs: [String] = []
|
||||
|
||||
@Relationship(inverse: \RitualArc.habits)
|
||||
var arc: RitualArc?
|
||||
|
||||
@ -73,24 +73,24 @@ enum TimeOfDay: String, Codable, CaseIterable, Comparable {
|
||||
/// This allows rituals to be renewed while preserving historical accuracy.
|
||||
@Model
|
||||
final class Ritual {
|
||||
var id: UUID
|
||||
var title: String
|
||||
var theme: String
|
||||
var notes: String
|
||||
var id: UUID = UUID()
|
||||
var title: String = ""
|
||||
var theme: String = ""
|
||||
var notes: String = ""
|
||||
|
||||
// Default duration for new arcs
|
||||
var defaultDurationDays: Int
|
||||
var defaultDurationDays: Int = 28
|
||||
|
||||
// Scheduling
|
||||
var timeOfDay: TimeOfDay
|
||||
var timeOfDay: TimeOfDay = .anytime
|
||||
|
||||
// Organization
|
||||
var iconName: String
|
||||
var category: String
|
||||
var iconName: String = "sparkles"
|
||||
var category: String = ""
|
||||
|
||||
// Arcs - each arc represents a time-bound period with its own habits
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var arcs: [RitualArc]
|
||||
var arcs: [RitualArc]? = []
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
@ -118,7 +118,7 @@ final class Ritual {
|
||||
|
||||
/// The currently active arc, if any.
|
||||
var currentArc: RitualArc? {
|
||||
arcs.first { $0.isActive }
|
||||
arcs?.first { $0.isActive }
|
||||
}
|
||||
|
||||
/// Whether this ritual has an active arc in progress.
|
||||
@ -128,7 +128,7 @@ final class Ritual {
|
||||
|
||||
/// All arcs sorted by start date (newest first).
|
||||
var sortedArcs: [RitualArc] {
|
||||
arcs.sorted { $0.startDate > $1.startDate }
|
||||
(arcs ?? []).sorted { $0.startDate > $1.startDate }
|
||||
}
|
||||
|
||||
/// The most recent arc (active or completed).
|
||||
@ -138,12 +138,12 @@ final class Ritual {
|
||||
|
||||
/// Total number of completed arcs.
|
||||
var completedArcCount: Int {
|
||||
arcs.filter { !$0.isActive }.count
|
||||
(arcs ?? []).filter { !$0.isActive }.count
|
||||
}
|
||||
|
||||
/// The end date of the most recently completed arc, if any.
|
||||
var lastCompletedDate: Date? {
|
||||
arcs.filter { !$0.isActive }
|
||||
(arcs ?? []).filter { !$0.isActive }
|
||||
.sorted { $0.endDate > $1.endDate }
|
||||
.first?.endDate
|
||||
}
|
||||
@ -174,12 +174,12 @@ final class Ritual {
|
||||
|
||||
/// Returns the arc that was active on a specific date, if any.
|
||||
func arc(for date: Date) -> RitualArc? {
|
||||
arcs.first { $0.contains(date: date) }
|
||||
(arcs ?? []).first { $0.contains(date: date) }
|
||||
}
|
||||
|
||||
/// Returns all arcs that overlap with a date range.
|
||||
func arcs(in range: ClosedRange<Date>) -> [RitualArc] {
|
||||
arcs.filter { arc in
|
||||
(arcs ?? []).filter { arc in
|
||||
// Arc overlaps if its range intersects with the query range
|
||||
arc.endDate >= range.lowerBound && arc.startDate <= range.upperBound
|
||||
}
|
||||
|
||||
@ -6,14 +6,14 @@ import SwiftData
|
||||
/// the old arc's data remains frozen for historical accuracy.
|
||||
@Model
|
||||
final class RitualArc {
|
||||
var id: UUID
|
||||
var startDate: Date
|
||||
var endDate: Date
|
||||
var arcNumber: Int
|
||||
var isActive: Bool
|
||||
var id: UUID = UUID()
|
||||
var startDate: Date = Date()
|
||||
var endDate: Date = Date()
|
||||
var arcNumber: Int = 1
|
||||
var isActive: Bool = true
|
||||
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var habits: [ArcHabit]
|
||||
var habits: [ArcHabit]? = []
|
||||
|
||||
@Relationship(inverse: \Ritual.arcs)
|
||||
var ritual: Ritual?
|
||||
@ -88,7 +88,7 @@ final class RitualArc {
|
||||
let newStartDate = calendar.date(byAdding: .day, value: 1, to: endDate) ?? Date()
|
||||
let newDuration = durationDays ?? self.durationDays
|
||||
|
||||
let copiedHabits = habits.map { $0.copyForNewArc() }
|
||||
let copiedHabits = (habits ?? []).map { $0.copyForNewArc() }
|
||||
|
||||
return RitualArc(
|
||||
startDate: newStartDate,
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
enum PerformanceLogger {
|
||||
private static let logger = Logger(
|
||||
subsystem: Bundle.main.bundleIdentifier ?? "Andromida",
|
||||
category: "Performance"
|
||||
)
|
||||
|
||||
static func measure<T>(_ name: String, _ block: () -> T) -> T {
|
||||
#if DEBUG
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
let result = block()
|
||||
let duration = CFAbsoluteTimeGetCurrent() - start
|
||||
logger.info("\(name, privacy: .public) took \(duration, format: .fixed(precision: 3))s")
|
||||
return result
|
||||
#else
|
||||
return block()
|
||||
#endif
|
||||
}
|
||||
|
||||
static func logDuration(_ name: String, from start: CFAbsoluteTime) {
|
||||
#if DEBUG
|
||||
let duration = CFAbsoluteTimeGetCurrent() - start
|
||||
logger.info("\(name, privacy: .public) took \(duration, format: .fixed(precision: 3))s")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -72,11 +72,9 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
/// Refreshes rituals and derived state for current date/time.
|
||||
func refresh() {
|
||||
PerformanceLogger.measure("RitualStore.refresh") {
|
||||
reloadRituals()
|
||||
checkForCompletedArcs()
|
||||
}
|
||||
}
|
||||
|
||||
func ritualProgress(for ritual: Ritual) -> Double {
|
||||
let habits = ritual.habits
|
||||
@ -174,12 +172,12 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
/// Returns all arcs that were active on a specific date.
|
||||
func arcsActive(on date: Date) -> [RitualArc] {
|
||||
rituals.flatMap { $0.arcs }.filter { $0.contains(date: date) }
|
||||
rituals.flatMap { $0.arcs ?? [] }.filter { $0.contains(date: date) }
|
||||
}
|
||||
|
||||
/// Returns habits from all arcs that were active on a specific date.
|
||||
func habitsActive(on date: Date) -> [ArcHabit] {
|
||||
arcsActive(on: date).flatMap { $0.habits }
|
||||
arcsActive(on: date).flatMap { $0.habits ?? [] }
|
||||
}
|
||||
|
||||
/// Checks if a ritual's current arc has completed (past end date).
|
||||
@ -217,7 +215,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
let newHabits: [ArcHabit]
|
||||
if copyHabits, let previousArc = ritual.latestArc {
|
||||
newHabits = previousArc.habits.map { $0.copyForNewArc() }
|
||||
newHabits = (previousArc.habits ?? []).map { $0.copyForNewArc() }
|
||||
} else {
|
||||
newHabits = []
|
||||
}
|
||||
@ -230,7 +228,9 @@ final class RitualStore: RitualStoreProviding {
|
||||
habits: newHabits
|
||||
)
|
||||
|
||||
ritual.arcs.append(newArc)
|
||||
var arcs = ritual.arcs ?? []
|
||||
arcs.append(newArc)
|
||||
ritual.arcs = arcs
|
||||
saveContext()
|
||||
}
|
||||
|
||||
@ -360,7 +360,10 @@ final class RitualStore: RitualStoreProviding {
|
||||
var breakdown: [BreakdownItem] = []
|
||||
|
||||
// Total check-ins
|
||||
let totalCheckIns = rituals.flatMap { $0.arcs }.flatMap { $0.habits }.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
let totalCheckIns = rituals
|
||||
.flatMap { $0.arcs ?? [] }
|
||||
.flatMap { $0.habits ?? [] }
|
||||
.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
breakdown.append(BreakdownItem(
|
||||
label: String(localized: "Total check-ins"),
|
||||
value: "\(totalCheckIns)"
|
||||
@ -392,7 +395,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
// Per-ritual breakdown
|
||||
for ritual in rituals {
|
||||
let ritualDays = Set(ritual.arcs.flatMap { $0.habits }.flatMap { $0.completedDayIDs }).count
|
||||
let ritualDays = Set((ritual.arcs ?? []).flatMap { $0.habits ?? [] }.flatMap { $0.completedDayIDs }).count
|
||||
breakdown.append(BreakdownItem(
|
||||
label: ritual.title,
|
||||
value: String(localized: "\(ritualDays) days")
|
||||
@ -409,9 +412,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
func refreshInsightCardsIfNeeded() {
|
||||
guard insightCardsNeedRefresh else { return }
|
||||
cachedInsightCards = PerformanceLogger.measure("RitualStore.insightCards") {
|
||||
computeInsightCards()
|
||||
}
|
||||
cachedInsightCards = computeInsightCards()
|
||||
insightCardsNeedRefresh = false
|
||||
}
|
||||
|
||||
@ -630,14 +631,18 @@ final class RitualStore: RitualStoreProviding {
|
||||
func addHabit(to ritual: Ritual, title: String, symbolName: String) {
|
||||
guard let arc = ritual.currentArc else { return }
|
||||
let habit = ArcHabit(title: title, symbolName: symbolName)
|
||||
arc.habits.append(habit)
|
||||
var habits = arc.habits ?? []
|
||||
habits.append(habit)
|
||||
arc.habits = habits
|
||||
saveContext()
|
||||
}
|
||||
|
||||
/// Removes a habit from the current arc of a ritual
|
||||
func removeHabit(_ habit: ArcHabit, from ritual: Ritual) {
|
||||
guard let arc = ritual.currentArc else { return }
|
||||
arc.habits.removeAll { $0.id == habit.id }
|
||||
var habits = arc.habits ?? []
|
||||
habits.removeAll { $0.id == habit.id }
|
||||
arc.habits = habits
|
||||
modelContext.delete(habit)
|
||||
saveContext()
|
||||
}
|
||||
@ -649,7 +654,6 @@ final class RitualStore: RitualStoreProviding {
|
||||
}
|
||||
|
||||
private func reloadRituals() {
|
||||
PerformanceLogger.measure("RitualStore.reloadRituals") {
|
||||
do {
|
||||
rituals = try modelContext.fetch(FetchDescriptor<Ritual>())
|
||||
updateDerivedData()
|
||||
@ -659,7 +663,6 @@ final class RitualStore: RitualStoreProviding {
|
||||
lastErrorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveContext() {
|
||||
do {
|
||||
@ -692,12 +695,11 @@ final class RitualStore: RitualStoreProviding {
|
||||
}
|
||||
|
||||
private func computeDatesWithActivity() -> Set<Date> {
|
||||
PerformanceLogger.measure("RitualStore.computeDatesWithActivity") {
|
||||
var dates: Set<Date> = []
|
||||
|
||||
for ritual in rituals {
|
||||
for arc in ritual.arcs {
|
||||
for habit in arc.habits {
|
||||
for arc in ritual.arcs ?? [] {
|
||||
for habit in arc.habits ?? [] {
|
||||
for dayID in habit.completedDayIDs {
|
||||
if let date = dayFormatter.date(from: dayID) {
|
||||
dates.insert(calendar.startOfDay(for: date))
|
||||
@ -709,7 +711,6 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
return dates
|
||||
}
|
||||
}
|
||||
|
||||
private func computePerfectDays(from activeDates: Set<Date>) -> Set<String> {
|
||||
guard !activeDates.isEmpty else { return [] }
|
||||
@ -756,7 +757,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
if let ritual = ritual {
|
||||
// Get habits from the arc that was active on this date
|
||||
if let arc = ritual.arc(for: date) {
|
||||
habits = arc.habits
|
||||
habits = arc.habits ?? []
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
@ -792,7 +793,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
if let ritual = ritual {
|
||||
// Get habits from the arc that was active on this date
|
||||
if let arc = ritual.arc(for: date) {
|
||||
for habit in arc.habits {
|
||||
for habit in arc.habits ?? [] {
|
||||
completions.append(HabitCompletion(
|
||||
habit: habit,
|
||||
ritualTitle: ritual.title,
|
||||
@ -804,7 +805,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
// Get all habits from all arcs that were active on this date
|
||||
for r in rituals {
|
||||
if let arc = r.arc(for: date) {
|
||||
for habit in arc.habits {
|
||||
for habit in arc.habits ?? [] {
|
||||
completions.append(HabitCompletion(
|
||||
habit: habit,
|
||||
ritualTitle: r.title,
|
||||
@ -885,14 +886,16 @@ final class RitualStore: RitualStoreProviding {
|
||||
|
||||
/// Returns the current streak for a specific ritual's current arc.
|
||||
func streakForRitual(_ ritual: Ritual) -> Int {
|
||||
guard let arc = ritual.currentArc, !arc.habits.isEmpty else { return 0 }
|
||||
guard let arc = ritual.currentArc else { return 0 }
|
||||
let habits = arc.habits ?? []
|
||||
guard !habits.isEmpty else { return 0 }
|
||||
|
||||
var streak = 0
|
||||
var checkDate = calendar.startOfDay(for: Date())
|
||||
|
||||
while arc.contains(date: checkDate) {
|
||||
let dayID = dayIdentifier(for: checkDate)
|
||||
let allCompleted = arc.habits.allSatisfy { $0.completedDayIDs.contains(dayID) }
|
||||
let allCompleted = habits.allSatisfy { $0.completedDayIDs.contains(dayID) }
|
||||
|
||||
if allCompleted {
|
||||
streak += 1
|
||||
@ -993,7 +996,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
// Update each ritual's arcs to cover a longer period
|
||||
for ritual in rituals {
|
||||
// For each arc (active or not), extend it to cover the demo period
|
||||
for arc in ritual.arcs {
|
||||
for arc in ritual.arcs ?? [] {
|
||||
// Set the arc to start 6 months ago and be active
|
||||
arc.startDate = sixMonthsAgo
|
||||
arc.endDate = calendar.date(byAdding: .day, value: 180 + 28 - 1, to: sixMonthsAgo) ?? today
|
||||
@ -1001,7 +1004,7 @@ final class RitualStore: RitualStoreProviding {
|
||||
}
|
||||
|
||||
// If no arcs exist, create one
|
||||
if ritual.arcs.isEmpty {
|
||||
if (ritual.arcs ?? []).isEmpty {
|
||||
let demoHabits = [
|
||||
ArcHabit(title: "Demo Habit 1", symbolName: "star.fill"),
|
||||
ArcHabit(title: "Demo Habit 2", symbolName: "heart.fill")
|
||||
@ -1013,7 +1016,9 @@ final class RitualStore: RitualStoreProviding {
|
||||
isActive: true,
|
||||
habits: demoHabits
|
||||
)
|
||||
ritual.arcs.append(demoArc)
|
||||
var arcs = ritual.arcs ?? []
|
||||
arcs.append(demoArc)
|
||||
ritual.arcs = arcs
|
||||
}
|
||||
}
|
||||
|
||||
@ -1024,11 +1029,11 @@ final class RitualStore: RitualStoreProviding {
|
||||
let dayID = dayIdentifier(for: currentDate)
|
||||
|
||||
for ritual in rituals {
|
||||
for arc in ritual.arcs {
|
||||
for arc in ritual.arcs ?? [] {
|
||||
// Only generate completions if the arc covers this date
|
||||
guard arc.contains(date: currentDate) else { continue }
|
||||
|
||||
for habit in arc.habits {
|
||||
for habit in arc.habits ?? [] {
|
||||
// Random completion with ~70% average success rate
|
||||
let threshold = Double.random(in: 0.5...0.9)
|
||||
let shouldComplete = Double.random(in: 0...1) < threshold
|
||||
@ -1049,8 +1054,8 @@ final class RitualStore: RitualStoreProviding {
|
||||
/// Clears all completion data (for testing).
|
||||
func clearAllCompletions() {
|
||||
for ritual in rituals {
|
||||
for arc in ritual.arcs {
|
||||
for habit in arc.habits {
|
||||
for arc in ritual.arcs ?? [] {
|
||||
for habit in arc.habits ?? [] {
|
||||
habit.completedDayIDs.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,12 +88,10 @@ 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 = PerformanceLogger.measure("HistoryMonthView.progressValues.\(monthTitle)") {
|
||||
dates.map { date -> Double? in
|
||||
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
|
||||
|
||||
@ -32,7 +32,6 @@ struct HistoryView: View {
|
||||
/// - Expanded: Up to 12 months of history
|
||||
/// Months are ordered oldest first, newest last (chronological order)
|
||||
private var months: [Date] {
|
||||
PerformanceLogger.measure("HistoryView.months") {
|
||||
let today = Date()
|
||||
let currentMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: today)) ?? today
|
||||
|
||||
@ -55,7 +54,6 @@ struct HistoryView: View {
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there's more history available beyond what's shown
|
||||
private var hasMoreHistory: Bool {
|
||||
@ -200,7 +198,6 @@ struct HistoryView: View {
|
||||
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())
|
||||
|
||||
@ -217,9 +214,7 @@ struct HistoryView: View {
|
||||
result[normalizedDate] = store.completionRate(for: normalizedDate, ritual: selected)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
let cache = result
|
||||
cachedProgressByDate = cache
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,11 +36,11 @@ struct RitualDetailView: View {
|
||||
}
|
||||
|
||||
private var hasMultipleArcs: Bool {
|
||||
ritual.arcs.count > 1
|
||||
(ritual.arcs ?? []).count > 1
|
||||
}
|
||||
|
||||
private var completedArcs: [RitualArc] {
|
||||
ritual.arcs.filter { !$0.isActive }.sorted { $0.startDate > $1.startDate }
|
||||
(ritual.arcs ?? []).filter { !$0.isActive }.sorted { $0.startDate > $1.startDate }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -353,7 +353,7 @@ struct RitualDetailView: View {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
||||
SectionHeaderView(
|
||||
title: String(localized: "Arc History"),
|
||||
subtitle: ritual.arcs.isEmpty ? nil : String(localized: "\(ritual.arcs.count) total")
|
||||
subtitle: (ritual.arcs ?? []).isEmpty ? nil : String(localized: "\((ritual.arcs ?? []).count) total")
|
||||
)
|
||||
|
||||
if completedArcs.isEmpty {
|
||||
@ -374,8 +374,9 @@ struct RitualDetailView: View {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
|
||||
let totalCheckIns = arc.habits.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
let possibleCheckIns = arc.habits.count * arc.durationDays
|
||||
let habits = arc.habits ?? []
|
||||
let totalCheckIns = habits.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
let possibleCheckIns = habits.count * arc.durationDays
|
||||
let completionRate = possibleCheckIns > 0 ? Int(Double(totalCheckIns) / Double(possibleCheckIns) * 100) : 0
|
||||
|
||||
return HStack {
|
||||
|
||||
@ -24,8 +24,9 @@ struct ArcRenewalSheet: View {
|
||||
|
||||
private var arcSummary: String {
|
||||
guard let arc = completedArc else { return "" }
|
||||
let totalHabits = arc.habits.count
|
||||
let totalCheckIns = arc.habits.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
let habits = arc.habits ?? []
|
||||
let totalHabits = habits.count
|
||||
let totalCheckIns = habits.reduce(0) { $0 + $1.completedDayIDs.count }
|
||||
let possibleCheckIns = totalHabits * arc.durationDays
|
||||
let rate = possibleCheckIns > 0 ? Int(Double(totalCheckIns) / Double(possibleCheckIns) * 100) : 0
|
||||
return String(localized: "\(rate)% completion over \(arc.durationDays) days")
|
||||
@ -94,7 +95,7 @@ struct ArcRenewalSheet: View {
|
||||
.font(.subheadline)
|
||||
|
||||
if let arc = completedArc {
|
||||
let habitCount = arc.habits.count
|
||||
let habitCount = (arc.habits ?? []).count
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(AppAccent.primary)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import SwiftUI
|
||||
import Bedrock
|
||||
import Foundation
|
||||
|
||||
struct RootView: View {
|
||||
@Bindable var store: RitualStore
|
||||
@ -85,9 +84,7 @@ struct RootView: View {
|
||||
Task {
|
||||
// Let tab selection UI update before refreshing data.
|
||||
await Task.yield()
|
||||
let refreshStart = CFAbsoluteTimeGetCurrent()
|
||||
store.refresh()
|
||||
PerformanceLogger.logDuration("RootView.refreshCurrentTab.store.refresh", from: refreshStart)
|
||||
analyticsPrewarmTask?.cancel()
|
||||
if selectedTab != .insights {
|
||||
analyticsPrewarmTask = Task { @MainActor in
|
||||
@ -98,12 +95,8 @@ struct RootView: View {
|
||||
}
|
||||
}
|
||||
if selectedTab == .settings {
|
||||
let settingsStart = CFAbsoluteTimeGetCurrent()
|
||||
settingsStore.refresh()
|
||||
PerformanceLogger.logDuration("RootView.refreshCurrentTab.settings.refresh", from: settingsStart)
|
||||
let reminderStart = CFAbsoluteTimeGetCurrent()
|
||||
await store.reminderScheduler.refreshStatus()
|
||||
PerformanceLogger.logDuration("RootView.refreshCurrentTab.reminderStatus", from: reminderStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user