Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions DevLog/Presentation/ViewModel/TodoEditorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import OrderedCollections
@Observable
final class TodoEditorViewModel: Store {
private struct Draft: Equatable {
let isCompleted: Bool
let isPinned: Bool
let title: String
let content: String
Expand All @@ -19,6 +20,7 @@ final class TodoEditorViewModel: Store {
let kind: TodoKind

init(todo: Todo) {
self.isCompleted = todo.isCompleted
self.isPinned = todo.isPinned
self.title = todo.title
self.content = todo.content
Expand All @@ -28,6 +30,7 @@ final class TodoEditorViewModel: Store {
}

init(state: State) {
self.isCompleted = state.isCompleted
self.isPinned = state.isPinned
self.title = state.title
self.content = state.content
Expand All @@ -38,6 +41,7 @@ final class TodoEditorViewModel: Store {
}

struct State: Equatable {
var isCompleted: Bool = false
var isPinned: Bool = false
var title: String = ""
var content: String = ""
Expand All @@ -61,6 +65,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)
Expand Down Expand Up @@ -117,6 +122,7 @@ final class TodoEditorViewModel: Store {
self.createdAt = todo.createdAt
self.completedAt = todo.completedAt
self.originalDraft = Draft(todo: todo)
state.isCompleted = todo.isCompleted
state.isPinned = todo.isPinned
state.title = todo.title
state.content = todo.content
Expand Down Expand Up @@ -145,6 +151,8 @@ final class TodoEditorViewModel: Store {
} else {
state.dueDate = nil
}
case .setCompleted(let isCompleted):
state.isCompleted = isCompleted
case .setKind(let todoKind):
state.kind = todoKind
case .setPinned(let isPinned):
Expand Down Expand Up @@ -183,13 +191,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.isCompleted ? (self.completedAt ?? date) : nil,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

makeTodo() 메서드에서 completedAt을 결정하는 로직에 버그가 있습니다.

현재 로직은 할 일을 '완료' 상태로 저장할 때, 이전에 완료된 적이 있었다면 원래의 완료 시간을 그대로 사용합니다 (self.completedAt). 이로 인해 다음과 같은 엣지 케이스에서 의도치 않은 동작이 발생합니다.

  1. 이미 완료된 할 일 (완료 시간: T1)의 편집 화면으로 진입합니다.
  2. '완료' 토글을 해제하여 '미완료' 상태로 변경합니다.
  3. 다시 '완료' 토글을 설정하여 '완료' 상태로 변경합니다 (이때의 시간: T2).
  4. 저장하면, completedAt이 T2가 아닌 T1으로 저장됩니다.

재완료 시에는 완료 시간이 새로고침되어야 합니다. 이 문제를 해결하려면 completedAt의 상태 관리 방식을 변경하는 것을 고려해볼 수 있습니다. 예를 들어, State 구조체에 completedAt을 포함시키고, setCompleted 액션을 처리할 때 '미완료'에서 '완료'로 상태가 변경되는 시점을 감지하여 completedAt을 현재 시간으로 업데이트하는 방식이 더 견고할 것 같습니다.

dueDate: state.dueDate,
tags: state.tags.map { $0 },
kind: state.kind
Expand Down
102 changes: 96 additions & 6 deletions DevLog/UI/Home/TodoDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}
}
11 changes: 9 additions & 2 deletions DevLog/UI/Home/TodoEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand Down