import SwiftUI import SwiftData @main struct ExpirannerApp: App { private let container: ModelContainer init() { let schema = Schema([Store.self, ProductRecord.self]) do { container = try ModelContainer(for: schema) } catch { // 이전 버전 저장소와 호환되지 않으면 저장소를 초기화하고 다시 시도한다. print("저장소 열기 실패, 데이터를 재설정합니다: \(error)") Self.removeDefaultStoreFiles() do { container = try ModelContainer(for: schema) } catch { fatalError("SwiftData 저장소를 만들 수 없습니다: \(error)") } } } private static func removeDefaultStoreFiles() { let dir = URL.applicationSupportDirectory for name in ["default.store", "default.store-shm", "default.store-wal"] { try? FileManager.default.removeItem(at: dir.appending(path: name)) } } var body: some Scene { WindowGroup { RootView() .tint(Theme.accent) } .modelContainer(container) } } /// 시스템 런치 스크린에서 자연스럽게 이어지는 인앱 스플래시. private struct RootView: View { @AppStorage(appearanceStorageKey) private var appearanceRaw = AppearanceMode.dark.rawValue @State private var showSplash = true var body: some View { ZStack { StoreListView() if showSplash { SplashView() .transition(.opacity) .zIndex(1) } } .preferredColorScheme(AppearanceMode(rawValue: appearanceRaw)?.colorScheme) .task { try? await Task.sleep(for: .seconds(1.0)) withAnimation(.easeOut(duration: 0.4)) { showSplash = false } } } } private struct SplashView: View { @State private var appeared = false var body: some View { ZStack { Theme.bg.ignoresSafeArea() VStack(spacing: 18) { Image("LaunchIcon") .resizable() .scaledToFit() .frame(width: 110, height: 110) .scaleEffect(appeared ? 1 : 0.9) .shadow(color: Theme.accent.opacity(0.25), radius: 30) VStack(spacing: 6) { Text("Expiranner") .font(.system(size: 26, weight: .bold, design: .rounded)) .foregroundStyle(Theme.textPrimary) Text("유통기한 점검") .font(.footnote.weight(.semibold)) .foregroundStyle(Theme.accent) .kerning(1) } .opacity(appeared ? 1 : 0) .offset(y: appeared ? 0 : 8) } } .onAppear { withAnimation(.spring(duration: 0.6)) { appeared = true } } } }