mycode/myApp/HaruDanim/Shared/DiaryModels.swift
songyc macbook 64c9f2d619 feat(diary): text boxes, page reorder, read-only browsing after expiry
- 텍스트 상자 요소: 노트 페이지 툴바에 '텍스트' 추가 — 키보드로 쓰는
  글 상자를 배치 요소로 올린다 (배치 모드에서 이동·크기 조절, 선택 시
  '글 수정'으로 내용·색 편집, 내보내기에도 그대로 렌더).
  DiaryPageItem에 text 필드 추가(kind .text, CloudKit 기본값 규칙 준수)
- 페이지 순서 변경: ⋯ 메뉴 → '페이지 순서 변경' 시트에서 노트 페이지를
  드래그로 재배열 (요약 페이지는 항상 첫 장)
- 프리미엄 만료 후 열람 허용: 이미 쓴 일기가 있으면 잠금 화면 대신
  달력을 열람 전용으로 연다 — 안내 배너 + 일기 있는 날짜만 활성,
  진입만으로 엔트리를 만들지 않고, 기분/할일/필기/필터/캘린더 등
  모든 편집 UI 비활성 (environment diaryReadOnly로 전파).
  일기를 쓴 적 없는 미구매 사용자는 기존 잠금 안내 유지

검증(iPad): 시드된 노트 페이지에서 텍스트 상자 렌더·툴바 확인,
-premium NO에서 배너/날짜 비활성/편집 UI 숨김 스크린샷 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-14 00:13:11 +09:00

163 lines
5.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
/// ID (EKEvent.eventIdentifier).
/// .
var calendarEventIDs: [String] = []
/// uuid ( ).
/// , .
var hiddenActionIDs: [String] = []
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
/// ( )
var text: String = ""
/// · (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
case text
/// (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
case .text: return 0.4
}
}
}