Files
Spanish/Conjuga/Conjuga/Views/Practice/Lyrics/LyricsLibraryView.swift
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

108 lines
3.3 KiB
Swift

import SwiftUI
import SharedModels
import SwiftData
struct LyricsLibraryView: View {
@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 {
ContentUnavailableView(
"No Songs Yet",
systemImage: "music.note.list",
description: Text("Tap + to search for Spanish song lyrics.")
)
} else {
List {
ForEach(songs) { song in
NavigationLink {
LyricsReaderView(song: song)
} label: {
SongRowView(song: song)
}
}
.onDelete(perform: deleteSongs)
}
}
}
.navigationTitle("Lyrics")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showingSearch = true
} label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showingSearch) {
NavigationStack {
LyricsSearchView {
showingSearch = false
loadSongs()
}
}
}
.onAppear(perform: loadSongs)
}
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 {
cloudModelContext.delete(songs[index])
}
try? cloudModelContext.save()
loadSongs()
}
}
// MARK: - Song Row
private struct SongRowView: View {
let song: SavedSong
var body: some View {
HStack(spacing: 12) {
if !song.albumArtURL.isEmpty, let url = URL(string: song.albumArtURL) {
AsyncImage(url: url) { image in
image.resizable().scaledToFill()
} placeholder: {
RoundedRectangle(cornerRadius: 6)
.fill(.fill.quaternary)
}
.frame(width: 50, height: 50)
.clipShape(RoundedRectangle(cornerRadius: 6))
} else {
Image(systemName: "music.note")
.font(.title2)
.foregroundStyle(.secondary)
.frame(width: 50, height: 50)
.background(.fill.quaternary, in: RoundedRectangle(cornerRadius: 6))
}
VStack(alignment: .leading, spacing: 2) {
Text(song.title)
.font(.subheadline.weight(.semibold))
.lineLimit(1)
Text(song.artist)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.padding(.vertical, 2)
}
}