Andromida/AndromidaWidget/Providers/AndromidaWidgetProvider.swift

115 lines
5.0 KiB
Swift

import WidgetKit
import SwiftUI
import SwiftData
import AppIntents
struct AndromidaWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> WidgetEntry {
WidgetEntry(
date: Date(),
configuration: ConfigurationAppIntent(),
completionRate: 0.75,
currentStreak: 5,
nextHabits: [
HabitEntry(id: UUID(), title: "Morning Meditation", symbolName: "figure.mind.and.body", ritualTitle: "Mindfulness", isCompleted: false),
HabitEntry(id: UUID(), title: "Drink Water", symbolName: "drop.fill", ritualTitle: "Health", isCompleted: true)
],
weeklyTrend: [0.5, 0.7, 0.6, 0.9, 0.8, 0.75, 0.0],
currentTimeOfDay: "Morning",
currentTimeOfDaySymbol: "sunrise.fill",
currentTimeOfDayRange: "Before 11am"
)
}
func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> WidgetEntry {
await fetchLatestData(for: configuration)
}
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<WidgetEntry> {
let entry = await fetchLatestData(for: configuration)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date()) ?? Date()
return Timeline(entries: [entry], policy: .after(nextUpdate))
}
@MainActor
private func fetchLatestData(for configuration: ConfigurationAppIntent) -> WidgetEntry {
let schema = Schema([Ritual.self, RitualArc.self, ArcHabit.self])
let configurationURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppIdentifiers.appGroupIdentifier)?
.appendingPathComponent("Andromida.sqlite") ?? URL.documentsDirectory.appendingPathComponent("Andromida.sqlite")
let modelConfig = ModelConfiguration(schema: schema, url: configurationURL)
do {
let container = try ModelContainer(for: schema, configurations: [modelConfig])
let context = container.mainContext
let descriptor = FetchDescriptor<Ritual>()
let rituals = try context.fetch(descriptor)
let today = Date()
let dayID = RitualAnalytics.dayIdentifier(for: today)
let timeOfDay = TimeOfDay.current(for: today)
// Match the app's logic for "Today" view
let todayRituals = RitualAnalytics.ritualsActive(on: today, from: rituals)
.sorted { lhs, rhs in
if lhs.timeOfDay != rhs.timeOfDay {
return lhs.timeOfDay < rhs.timeOfDay
}
return lhs.sortIndex < rhs.sortIndex
}
var visibleHabits: [HabitEntry] = []
for ritual in todayRituals {
if let arc = ritual.arcs?.first(where: { $0.isActive && $0.contains(date: today) }) {
// Sort habits within each ritual by their sortIndex
let sortedHabits = (arc.habits ?? []).sorted { $0.sortIndex < $1.sortIndex }
for habit in sortedHabits {
visibleHabits.append(HabitEntry(
id: habit.id,
title: habit.title,
symbolName: habit.symbolName,
ritualTitle: ritual.title,
isCompleted: habit.completedDayIDs.contains(dayID)
))
}
}
}
// 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)
// Streak calculation
let streak = RitualAnalytics.calculateCurrentStreak(rituals: rituals)
return WidgetEntry(
date: today,
configuration: configuration,
completionRate: overallRate,
currentStreak: streak,
nextHabits: Array(nextHabits),
weeklyTrend: [],
currentTimeOfDay: timeOfDay.displayName,
currentTimeOfDaySymbol: timeOfDay.symbolName,
currentTimeOfDayRange: timeOfDay.timeRange
)
} catch {
// Return a default entry instead of placeholder(in: .preview)
return WidgetEntry(
date: Date(),
configuration: configuration,
completionRate: 0.0,
currentStreak: 0,
nextHabits: [],
weeklyTrend: [],
currentTimeOfDay: "Today",
currentTimeOfDaySymbol: "clock.fill",
currentTimeOfDayRange: ""
)
}
}
}