Compare commits

...

2 Commits

Author SHA1 Message Date
ee8a0c478f Merge pull request 'Fix lyrics wiped on schema reset' (#8) from fix/lyrics-data-loss into main 2026-04-13 10:27:39 -05:00
Trey t
282cd1b3a3 Fix lyrics wiped on schema reset by moving SavedSong to cloud container
SavedSong was in the local container alongside reference data, so it
got deleted whenever localStoreResetVersion was bumped. Move it to the
cloud container (CloudKit-synced) so saved songs persist across schema
changes. Update lyrics views to use cloudModelContextProvider.

Closes #4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:27:21 -05:00
3 changed files with 24 additions and 13 deletions

View File

@@ -10,7 +10,7 @@ private enum CloudPreviewContainer {
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
return try! ModelContainer(
for: ReviewCard.self, CourseReviewCard.self, UserProgress.self,
TestResult.self, DailyLog.self,
TestResult.self, DailyLog.self, SavedSong.self,
configurations: configuration
)
}()
@@ -67,13 +67,13 @@ struct ConjugaApp: App {
"cloud",
schema: Schema([
ReviewCard.self, CourseReviewCard.self, UserProgress.self,
TestResult.self, DailyLog.self,
TestResult.self, DailyLog.self, SavedSong.self,
]),
cloudKitDatabase: .private("iCloud.com.conjuga.app")
)
cloudContainer = try ModelContainer(
for: ReviewCard.self, CourseReviewCard.self, UserProgress.self,
TestResult.self, DailyLog.self,
TestResult.self, DailyLog.self, SavedSong.self,
configurations: cloudConfig
)
} catch {
@@ -200,7 +200,6 @@ struct ConjugaApp: App {
schema: Schema([
Verb.self, VerbForm.self, IrregularSpan.self,
TenseGuide.self, CourseDeck.self, VocabCard.self,
SavedSong.self,
]),
url: url,
cloudKitDatabase: .none
@@ -208,7 +207,6 @@ struct ConjugaApp: App {
return try ModelContainer(
for: Verb.self, VerbForm.self, IrregularSpan.self,
TenseGuide.self, CourseDeck.self, VocabCard.self,
SavedSong.self,
configurations: localConfig
)
}
@@ -237,7 +235,7 @@ struct ConjugaApp: App {
/// Clears accumulated stale schema metadata from previous container configurations.
/// Bump the version number to force another reset if the schema changes again.
private static func performOneTimeLocalStoreResetIfNeeded(at url: URL) {
let resetVersion = 2 // bump: widget schema moved to SharedModels
let resetVersion = 3 // bump: SavedSong moved to cloud container
let key = "localStoreResetVersion"
let defaults = UserDefaults.standard

View File

@@ -7,9 +7,11 @@ struct LyricsConfirmationView: View {
let result: LyricsSearchResult
let onSave: () -> Void
@Environment(\.modelContext) private var modelContext
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
@Environment(\.dismiss) private var dismiss
private var cloudModelContext: ModelContext { cloudModelContextProvider() }
@State private var translatedEN = ""
@State private var isTranslating = true
@State private var translationError = false
@@ -207,8 +209,8 @@ struct LyricsConfirmationView: View {
albumArtURL: result.albumArtURL ?? "",
appleMusicURL: result.appleMusicURL ?? ""
)
modelContext.insert(song)
try? modelContext.save()
cloudModelContext.insert(song)
try? cloudModelContext.save()
onSave()
}
}

View File

@@ -3,9 +3,12 @@ import SharedModels
import SwiftData
struct LyricsLibraryView: View {
@Query(sort: \SavedSong.savedDate, order: .reverse) private var songs: [SavedSong]
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
@State private var songs: [SavedSong] = []
@State private var showingSearch = false
private var cloudModelContext: ModelContext { cloudModelContextProvider() }
var body: some View {
Group {
if songs.isEmpty {
@@ -42,18 +45,26 @@ struct LyricsLibraryView: View {
NavigationStack {
LyricsSearchView {
showingSearch = false
loadSongs()
}
}
}
.onAppear(perform: loadSongs)
}
@Environment(\.modelContext) private var modelContext
private func loadSongs() {
let descriptor = FetchDescriptor<SavedSong>(
sortBy: [SortDescriptor(\SavedSong.savedDate, order: .reverse)]
)
songs = (try? cloudModelContext.fetch(descriptor)) ?? []
}
private func deleteSongs(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(songs[index])
cloudModelContext.delete(songs[index])
}
try? modelContext.save()
try? cloudModelContext.save()
loadSongs()
}
}