mycode/myApp/HaruDanim/Shared/DiaryModels.swift
songyc macbook 40d917d4e2 feat(diary): add iPad-only premium diary tab with calendar, pencil notes, and export
- 달력(작성일 마커: 기분 이모지/연필 점) → 날짜별 일기, 이번 달 작성 수 표시
- 첫 페이지: 날짜·오늘 기분(이모지/사진, 끄기 옵션)·할 일 체크리스트(스크리블 입력)·
  타임테이블·통계(내보내기 스냅숏 재사용으로 화면·내보내기 일치)
- 노트 페이지: PencilKit 캔버스(도구 팔레트) + 줄 노트 배경 + 사진/도형(사각형·원·화살표·선)
  배치 모드(드래그·핀치), 페이지 무한 추가 — 논리 크기 768×1024 고정으로 WYSIWYG 내보내기
- 내보내기: 구간/복수 날짜(MultiDatePicker) → PDF 1개 또는 날짜별 세로 스티치 PNG
- SwiftData 모델 4종(CloudKit 호환 패턴) 스키마 추가, PremiumFeature.diary,
  iPhone 탭 컨텍스트(탭바·더보기·라디얼)에서 일기 제외, en/ja 번역 39건
- 검증 인자: -diarySeed/-diaryOpenToday/-diaryPage N/-diaryShowExport/-diaryExportRun pdf|image

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-13 02:51:30 +09:00

151 lines
4.4 KiB
Swift

//
// 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
/// 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
}
}
}