Migrate from Core Data to SwiftData

- Replace Core Data with SwiftData for iOS 18+
- Create MoodEntryModel as @Model class replacing MoodEntry entity
- Create SharedModelContainer for App Group container sharing
- Create DataController with CRUD extensions replacing PersistenceController
- Update all views and view models to use MoodEntryModel
- Update widget extension to use SwiftData
- Remove old Core Data files (Persistence*.swift, .xcdatamodeld)
- Add EntryType enum with all entry type cases
- Fix widget label truncation with proper spacing and text scaling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-10 15:08:05 -06:00
parent 443f4dfc55
commit aaaf04f05e
51 changed files with 926 additions and 962 deletions

View File

@@ -33,7 +33,7 @@ struct VoteMoodIntent: AppIntent {
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
// Add mood entry
PersistenceController.shared.add(mood: mood, forDate: votingDate, entryType: .widget)
DataController.shared.add(mood: mood, forDate: votingDate, entryType: .widget)
// Store last voted date
let dateString = ISO8601DateFormatter().string(from: Calendar.current.startOfDay(for: votingDate))
@@ -63,19 +63,24 @@ struct VoteWidgetProvider: TimelineProvider {
completion(entry)
return
}
let entry = createEntry()
completion(entry)
Task { @MainActor in
let entry = createEntry()
completion(entry)
}
}
func getTimeline(in context: Context, completion: @escaping (Timeline<VoteWidgetEntry>) -> Void) {
let entry = createEntry()
Task { @MainActor in
let entry = createEntry()
// Refresh at midnight
let midnight = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!)
let timeline = Timeline(entries: [entry], policy: .after(midnight))
completion(timeline)
// Refresh at midnight
let midnight = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!)
let timeline = Timeline(entries: [entry], policy: .after(midnight))
completion(timeline)
}
}
@MainActor
private func createEntry() -> VoteWidgetEntry {
let hasSubscription = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.hasActiveSubscription.rawValue)
@@ -84,7 +89,7 @@ struct VoteWidgetProvider: TimelineProvider {
let dayEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: dayStart)!
// Check if user has voted today
let todayEntry = PersistenceController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first
let todayEntry = DataController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first
let hasVotedToday = todayEntry != nil && todayEntry?.mood != Mood.missing && todayEntry?.mood != Mood.placeholder
// Get today's mood if voted
@@ -93,7 +98,7 @@ struct VoteWidgetProvider: TimelineProvider {
// Get stats for display after voting
var stats: MoodStats? = nil
if hasVotedToday {
let allEntries = PersistenceController.shared.getData(
let allEntries = DataController.shared.getData(
startDate: Date(timeIntervalSince1970: 0),
endDate: Date(),
includedDays: []
@@ -188,9 +193,10 @@ struct VotingView: View {
}
} else {
// Horizontal layout for medium/large
HStack(spacing: 12) {
HStack(spacing: 4) {
ForEach(moods, id: \.rawValue) { mood in
MoodButton(mood: mood, isCompact: false)
.frame(maxWidth: .infinity)
}
}
}
@@ -224,6 +230,8 @@ struct MoodButton: View {
Text(mood.widgetDisplayName)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
.minimumScaleFactor(0.7)
}
}
}

View File

@@ -8,7 +8,7 @@
import WidgetKit
import SwiftUI
import Intents
import CoreData
import SwiftData
class WatchTimelineView: Identifiable {
let id = UUID()
@@ -28,7 +28,7 @@ class WatchTimelineView: Identifiable {
}
struct TimeLineCreator {
static func createViews(daysBack: Int) -> [WatchTimelineView] {
@MainActor static func createViews(daysBack: Int) -> [WatchTimelineView] {
var timeLineView = [WatchTimelineView]()
let latestDayToShow = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
@@ -42,7 +42,7 @@ struct TimeLineCreator {
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
let moodImages: MoodImagable.Type = UserDefaultsStore.moodMoodImagable()
if let todayEntry = PersistenceController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first {
if let todayEntry = DataController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first {
timeLineView.append(WatchTimelineView(image: moodImages.icon(forMood: todayEntry.mood),
graphic: moodImages.icon(forMood: todayEntry.mood),
date: dayStart,
@@ -99,7 +99,7 @@ struct Provider: IntentTimelineProvider {
timeLineViews: TimeLineCreator.createSampleViews(count: 10))
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
@MainActor func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
// Use sample data for widget picker preview, real data otherwise
let timeLineViews: [WatchTimelineView]
if context.isPreview {
@@ -165,9 +165,6 @@ struct FeelsWidgetEntryView : View {
@unknown default:
fatalError()
}
}.onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange)) { _ in
// make sure you don't call this too often
WidgetCenter.shared.reloadAllTimelines()
}
}
}
@@ -294,10 +291,6 @@ struct FeelsGraphicWidgetEntryView : View {
@ViewBuilder
var body: some View {
SmallGraphicWidgetView(entry: entry)
.onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange)) { _ in
// make sure you don't call this too often
WidgetCenter.shared.reloadAllTimelines()
}
}
}