// // DiaryModels.swift // Haru_Danim // // 일기(다이어리) 데이터 모델 — iPad 전용 프리미엄 기능. // 하루(논리적 하루 키)당 DiaryEntry 하나: // - 첫 페이지(요약): 오늘 기분(이모지 또는 사진) + 할 일 체크리스트 (+ 그날의 기록/통계는 즉석 계산) // - 이후 페이지: PencilKit 자유 필기 캔버스(줄 노트 배경 선택) + 사진/도형 배치 // CloudKit 호환 규칙(모든 프로퍼티 기본값, to-many 관계 optional storage + computed 접근자)을 따른다. // import Foundation import SwiftData @Model final class DiaryEntry { var uuid: UUID = UUID() /// 논리적 하루 키 (DayMath.dayKey — 해당 달력일의 자정) var dayKey: Date = Date() /// 오늘 기분 이모지 (사진과 동시 사용 가능, 빈 문자열 = 미설정) var moodEmoji: String = "" /// 오늘 기분 사진 (사진 앨범에서 선택, nil = 미설정) @Attribute(.externalStorage) var moodImageData: Data? = nil var createdAt: Date = Date() var updatedAt: Date = Date() @Relationship(deleteRule: .cascade, inverse: \DiaryTodo.entry) var todosStorage: [DiaryTodo]? = [] @Relationship(deleteRule: .cascade, inverse: \DiaryPage.entry) var pagesStorage: [DiaryPage]? = [] init(dayKey: Date) { self.dayKey = dayKey } var todos: [DiaryTodo] { get { (todosStorage ?? []).sorted { $0.sortOrder < $1.sortOrder } } set { todosStorage = newValue } } var pages: [DiaryPage] { get { (pagesStorage ?? []).sorted { $0.index < $1.index } } set { pagesStorage = newValue } } /// 달력 마커 표시 기준: 무언가 하나라도 기록했으면 "작성함" var hasContent: Bool { !moodEmoji.isEmpty || moodImageData != nil || !(todosStorage ?? []).isEmpty || (pagesStorage ?? []).contains { $0.hasContent } } } /// 요약 페이지의 할 일 한 줄 (펜슬 탭으로 체크) @Model final class DiaryTodo { var uuid: UUID = UUID() var text: String = "" var isDone: Bool = false var sortOrder: Int = 0 var entry: DiaryEntry? init(text: String = "", sortOrder: Int = 0) { self.text = text self.sortOrder = sortOrder } } /// 자유 필기 노트 페이지 @Model final class DiaryPage { var uuid: UUID = UUID() /// 페이지 순서 (0 = 요약 다음 첫 노트) var index: Int = 0 /// true = 줄 노트 배경, false = 빈 캔버스 var lined: Bool = false /// 줄 노트 줄 간격 (pt, 768×1024 논리 페이지 기준) var lineSpacing: Double = 44 /// PKDrawing.dataRepresentation() @Attribute(.externalStorage) var drawingData: Data = Data() var entry: DiaryEntry? @Relationship(deleteRule: .cascade, inverse: \DiaryPageItem.page) var itemsStorage: [DiaryPageItem]? = [] init(index: Int) { self.index = index } var items: [DiaryPageItem] { get { (itemsStorage ?? []).sorted { $0.createdAt < $1.createdAt } } set { itemsStorage = newValue } } var hasContent: Bool { !drawingData.isEmpty || !(itemsStorage ?? []).isEmpty } } /// 노트 페이지 위에 배치하는 요소 (사진 또는 도형) @Model final class DiaryPageItem { var uuid: UUID = UUID() /// DiaryItemKind rawValue var kindRaw: String = DiaryItemKind.photo.rawValue /// 사진일 때만 사용 @Attribute(.externalStorage) var imageData: Data? = nil /// 도형 색 (hex) var colorHex: String = "#2F6B4F" /// 페이지 폭 대비 중심 좌표 비율 (0...1) — 페이지 크기가 달라져도 같은 자리에 놓인다 var centerX: Double = 0.5 var centerY: Double = 0.3 /// 페이지 폭 대비 너비 비율 var widthRatio: Double = 0.35 var rotationDegrees: Double = 0 var createdAt: Date = Date() var page: DiaryPage? init(kind: DiaryItemKind) { self.kindRaw = kind.rawValue } var kind: DiaryItemKind { get { DiaryItemKind(rawValue: kindRaw) ?? .photo } set { kindRaw = newValue.rawValue } } } nonisolated enum DiaryItemKind: String, CaseIterable { case photo case rectangle case ellipse case arrow case line /// 사진 대비 도형의 기본 세로 비율 (widthRatio 기준) var defaultAspect: Double { switch self { case .photo: return 1 case .rectangle: return 0.7 case .ellipse: return 0.7 case .arrow: return 0.35 case .line: return 0.1 } } }