diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 397a508..8125cce 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -11,6 +11,8 @@ import OrderedCollections @Observable final class TodoEditorViewModel: Store { private struct Draft: Equatable { + let isCompleted: Bool + let completedAt: Date? let isPinned: Bool let title: String let content: String @@ -19,6 +21,8 @@ final class TodoEditorViewModel: Store { let kind: TodoKind init(todo: Todo) { + self.isCompleted = todo.isCompleted + self.completedAt = todo.completedAt self.isPinned = todo.isPinned self.title = todo.title self.content = todo.content @@ -28,6 +32,8 @@ final class TodoEditorViewModel: Store { } init(state: State) { + self.isCompleted = state.isCompleted + self.completedAt = state.completedAt self.isPinned = state.isPinned self.title = state.title self.content = state.content @@ -38,6 +44,8 @@ final class TodoEditorViewModel: Store { } struct State: Equatable { + var isCompleted: Bool = false + var completedAt: Date? var isPinned: Bool = false var title: String = "" var content: String = "" @@ -61,6 +69,7 @@ final class TodoEditorViewModel: Store { case addTag(String) case removeTag(String) case setContent(String) + case setCompleted(Bool) case setDueDate(Date?) case setKind(TodoKind) case setPinned(Bool) @@ -78,7 +87,6 @@ final class TodoEditorViewModel: Store { private let isCompleted: Bool private let isChecked: Bool private let createdAt: Date? - private let completedAt: Date? private let originalDraft: Draft? var navigationTitle: String { @@ -104,7 +112,6 @@ final class TodoEditorViewModel: Store { self.isCompleted = false self.isChecked = false self.createdAt = nil - self.completedAt = nil self.originalDraft = nil state.kind = kind } @@ -115,8 +122,9 @@ final class TodoEditorViewModel: Store { self.isCompleted = todo.isCompleted self.isChecked = todo.isChecked self.createdAt = todo.createdAt - self.completedAt = todo.completedAt self.originalDraft = Draft(todo: todo) + state.isCompleted = todo.isCompleted + state.completedAt = todo.completedAt state.isPinned = todo.isPinned state.title = todo.title state.content = todo.content @@ -145,6 +153,11 @@ final class TodoEditorViewModel: Store { } else { state.dueDate = nil } + case .setCompleted(let isCompleted): + if state.isCompleted != isCompleted { + state.completedAt = isCompleted ? Date() : nil + } + state.isCompleted = isCompleted case .setKind(let todoKind): state.kind = todoKind case .setPinned(let isPinned): @@ -183,13 +196,13 @@ extension TodoEditorViewModel { return Todo( id: self.id, isPinned: state.isPinned, - isCompleted: self.isCompleted, + isCompleted: state.isCompleted, isChecked: self.isChecked, title: state.title, content: state.content, createdAt: self.createdAt ?? date, updatedAt: date, - completedAt: self.completedAt, + completedAt: state.completedAt, dueDate: state.dueDate, tags: state.tags.map { $0 }, kind: state.kind diff --git a/DevLog/UI/Home/TodoDetailView.swift b/DevLog/UI/Home/TodoDetailView.swift index 250ee91..6f08909 100644 --- a/DevLog/UI/Home/TodoDetailView.swift +++ b/DevLog/UI/Home/TodoDetailView.swift @@ -70,14 +70,104 @@ struct TodoDetailView: View { @ViewBuilder private var sheetContent: some View { if let todo = viewModel.state.todo { - TodoInfoSheetView( - createdAt: todo.createdAt, - completedAt: todo.completedAt, - dueDate: todo.dueDate, - tags: todo.tags - ) { + TodoDetailInfoSheetView(todo: todo) { viewModel.send(.setShowInfo(false)) } } } } + +private struct TodoDetailInfoSheetView: View { + let todo: Todo + let onClose: () -> Void + private let calendar = Calendar.current + + var body: some View { + NavigationStack { + List { + Section("옵션") { + HStack { + Text("카테고리") + Spacer() + Text(todo.kind.localizedName) + .foregroundStyle(.secondary) + } + + statusRow( + title: "완료", + systemImage: todo.isCompleted ? "checkmark.circle.fill" : "circle", + color: todo.isCompleted ? .green : .secondary + ) + + statusRow( + title: "중요 표시", + systemImage: todo.isPinned ? "star.fill" : "star", + color: todo.isPinned ? .orange : .secondary + ) + + HStack { + Text("마감일") + + Spacer() + + if let dueDate = todo.dueDate { + Tag(dueDateText(for: dueDate), isEditing: false) + .padding(.vertical, -4) + } else { + Text("없음") + .foregroundStyle(.secondary) + } + } + } + + Section("태그") { + if todo.tags.isEmpty { + Text("태그 없음") + .foregroundStyle(.secondary) + .padding(.vertical, 4) + } else { + TagList(todo.tags) + } + } + } + .navigationTitle("세부 정보") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarLeadingButton { + onClose() + } + } + } + } + + @ViewBuilder + private func statusRow( + title: String, + systemImage: String, + color: Color + ) -> some View { + HStack { + Text(title) + + Spacer() + + Image(systemName: systemImage) + .foregroundStyle(color) + } + } + + private func dueDateText(for dueDate: Date) -> String { + let currentYear = calendar.component(.year, from: Date()) + let dueDateYear = calendar.component(.year, from: dueDate) + + if currentYear == dueDateYear { + return dueDate.formatted( + .dateTime.month(.defaultDigits).day(.defaultDigits) + ) + } + + return dueDate.formatted( + .dateTime.year(.twoDigits).month(.defaultDigits).day(.defaultDigits) + ) + } +} diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index f8534a1..ebae63b 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -192,6 +192,15 @@ private struct TodoEditorInfoSheetView: View { } } + Toggle( + "완료", + isOn: Binding( + get: { viewModel.state.isCompleted }, + set: { viewModel.send(.setCompleted($0)) } + ) + ) + .tint(.blue) + Toggle( "중요 표시", isOn: Binding( @@ -263,9 +272,7 @@ private struct TodoEditorInfoSheetView: View { HStack { Text("마감일") .foregroundStyle(.primary) - Spacer() - if let dueDate = viewModel.state.dueDate { Tag(dueDateText(for: dueDate), isEditing: true) { viewModel.send(.setDueDate(nil))