115 lines
5.0 KiB
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: ""
|
|
)
|
|
}
|
|
}
|
|
}
|