From 215584f821f7ffa2ae9e0c11385f01412beb7578 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 1 Mar 2026 19:32:05 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20=ED=88=B4?= =?UTF-8?q?=EB=B0=94=20=ED=98=95=ED=83=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UI/Common/Componeent/ToolbarButton+.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 DevLog/UI/Common/Componeent/ToolbarButton+.swift diff --git a/DevLog/UI/Common/Componeent/ToolbarButton+.swift b/DevLog/UI/Common/Componeent/ToolbarButton+.swift new file mode 100644 index 0000000..12fce71 --- /dev/null +++ b/DevLog/UI/Common/Componeent/ToolbarButton+.swift @@ -0,0 +1,49 @@ +// +// ToolbarButton+.swift +// DevLog +// +// Created by 최윤진 on 3/1/26. +// + +import SwiftUI + +struct ToolbarLeadingButton: ToolbarContent { + var action: (() -> Void)? + + var body: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { + if #available(iOS 26.0, *) { + Button(role: .cancel) { + action?() + } + } else { + Button { + action?() + } label: { + Text("취소") + } + } + } + } +} + +struct ToolbarTrailingButton: ToolbarContent { + var action: (() -> Void)? + + var body: some ToolbarContent { + ToolbarItem(placement: .topBarTrailing) { + if #available(iOS 26.0, *) { + Button(role: .confirm) { + action?() + } + } else { + Button { + action?() + } label: { + Text("확인") + .bold() + } + } + } + } +} From e57ef44e1aefb1eeb6c263ec52010d69f0350941 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 1 Mar 2026 19:33:28 +0900 Subject: [PATCH 2/3] =?UTF-8?q?ui:=20=EB=A7=88=EA=B0=90=EC=9D=BC,=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=EB=A5=BC=20=EB=B3=84=EB=8F=84=20=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/TodoDetailViewModel.swift | 14 ++- DevLog/UI/Home/TodoDetailView.swift | 119 +++++++++++++----- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift index 850fba7..33cd7d9 100644 --- a/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoDetailViewModel.swift @@ -10,17 +10,19 @@ import Foundation final class TodoDetailViewModel: Store { struct State { var todo: Todo? - var isLoading = false - var showEditor = false - var showAlert = false - var alertTitle = "" - var alertMessage = "" + var isLoading: Bool = false + var showAlert: Bool = false + var showEditor: Bool = false + var showInfo: Bool = false + var alertTitle: String = "" + var alertMessage: String = "" } enum Action { case onAppear case setAlert(Bool) case setShowEditor(Bool) + case setShowInfo(Bool) case setTodo(Todo) case setLoading(Bool) case upsertTodo(Todo) @@ -57,6 +59,8 @@ final class TodoDetailViewModel: Store { setAlert(&state, isPresented: isPresented) case .setShowEditor(let isPresented): state.showEditor = isPresented + case .setShowInfo(let presented): + state.showInfo = presented case .setTodo(let todo): state.todo = todo case .setLoading(let value): diff --git a/DevLog/UI/Home/TodoDetailView.swift b/DevLog/UI/Home/TodoDetailView.swift index 575301c..52faa11 100644 --- a/DevLog/UI/Home/TodoDetailView.swift +++ b/DevLog/UI/Home/TodoDetailView.swift @@ -18,35 +18,8 @@ struct TodoDetailView: View { ScrollView { LazyVStack(alignment: .leading, spacing: 10) { Text(todo.title) - .font(.title3) + .font(.title3.bold()) .padding(.horizontal) - if let date = todo.dueDate { - Divider() - HStack { - Text("마감일") - Spacer() - Text(date.formatted(date: .long, time: .omitted)) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color.gray.opacity(0.2)) - ) - } - .padding(.horizontal) - } - Divider() - HStack { - Text("태그") - Divider() - ScrollView(.horizontal) { - HStack { - ForEach(todo.tags, id: \.self) { tag in - Tag(tag, isEditing: false) - } - } - } - .scrollIndicators(.never) - } - .padding(.horizontal) Divider() Markdown(todo.content) .padding(.horizontal) @@ -57,6 +30,12 @@ struct TodoDetailView: View { } } .onAppear { viewModel.send(.onAppear) } + .sheet(isPresented: Binding( + get: { viewModel.state.showInfo }, + set: { viewModel.send(.setShowInfo($0)) } + )) { + sheetContent + } .fullScreenCover(isPresented: Binding( get: { viewModel.state.showEditor }, set: { viewModel.send(.setShowEditor($0)) } @@ -68,12 +47,84 @@ struct TodoDetailView: View { ) } } - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button { - viewModel.send(.setShowEditor(true)) - } label: { - Text("수정") + .toolbar { toolbarContent } + } + + @ToolbarContentBuilder + private var toolbarContent: some ToolbarContent { + ToolbarItem(placement: .topBarTrailing) { + Button { + viewModel.send(.setShowInfo(true)) + } label: { + Image(systemName: "info.circle") + } + } + if #available(iOS 26.0, *) { + ToolbarSpacer(.fixed, placement: .topBarTrailing) + } + ToolbarItem(placement: .topBarTrailing) { + Button { + viewModel.send(.setShowEditor(true)) + } label: { + Text("수정") + } + } + } + + private var sheetContent: some View { + NavigationStack { + ScrollView { + LazyVStack(spacing: 32) { + VStack { + HStack { + Text("마감일") + .font(.subheadline) + .foregroundStyle(.secondary) + Spacer() + } + HStack(spacing: 8) { + Image(systemName: "calendar") + .foregroundStyle(.secondary) + Text( + viewModel.state.todo?.dueDate? + .formatted(date: .abbreviated, time: .omitted) + ?? "마감일 없음" + ) + .foregroundStyle( + viewModel.state.todo?.dueDate == nil ? .secondary : .primary + ) + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 12) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(Color(.tertiarySystemFill)) + ) + Divider() + } + VStack { + HStack { + Text("태그") + .font(.subheadline) + .foregroundStyle(.secondary) + Spacer() + } + Divider() + if let tags = viewModel.state.todo?.tags, !tags.isEmpty { + TagLayout { + ForEach(tags, id: \.self) { tag in + Tag(tag, isEditing: false) + } + } + } + } + } + .padding(.horizontal) + } + .toolbar { + ToolbarLeadingButton { + viewModel.send(.setShowInfo(false)) } } } From 9d6d5df3fd02acf4c9365e105dd0257a11e13816 Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 1 Mar 2026 19:45:05 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20SheetToolbar=20=EB=A5=BC=20Tool?= =?UTF-8?q?barLeadingButton=EA=B3=BC=20ToolbarTrailingButton=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UI/Common/Componeent/SheetToolbar.swift | 59 ------------------- .../UI/Common/Componeent/ToolbarButton+.swift | 13 ++++ DevLog/UI/Home/TodoEditorView.swift | 16 ++--- .../PushNotificationSettingsView.swift | 10 ++-- 4 files changed, 27 insertions(+), 71 deletions(-) delete mode 100644 DevLog/UI/Common/Componeent/SheetToolbar.swift diff --git a/DevLog/UI/Common/Componeent/SheetToolbar.swift b/DevLog/UI/Common/Componeent/SheetToolbar.swift deleted file mode 100644 index b42e149..0000000 --- a/DevLog/UI/Common/Componeent/SheetToolbar.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// SheetToolbar.swift -// DevLog -// -// Created by 최윤진 on 2/27/26. -// - -import SwiftUI - -struct SheetToolbar: ToolbarContent { - let onCancel: () -> Void - let onConfirm: () -> Void - let isConfirmEnabled: Bool - - init( - onCancel: @escaping () -> Void, - onConfirm: @escaping () -> Void, - isConfirmEnabled: Bool = true - ) { - self.onCancel = onCancel - self.onConfirm = onConfirm - self.isConfirmEnabled = isConfirmEnabled - } - - var body: some ToolbarContent { - if #available(iOS 26.0, *) { - ToolbarItem(placement: .topBarLeading) { - Button(role: .cancel) { - onCancel() - } - } - - ToolbarItem(placement: .topBarTrailing) { - Button(role: .confirm) { - onConfirm() - } - .disabled(!isConfirmEnabled) - } - } else { - ToolbarItem(placement: .topBarLeading) { - Button { - onCancel() - } label: { - Text("취소") - } - } - - ToolbarItem(placement: .topBarTrailing) { - Button { - onConfirm() - } label: { - Text("확인") - .bold() - } - .disabled(!isConfirmEnabled) - } - } - } -} diff --git a/DevLog/UI/Common/Componeent/ToolbarButton+.swift b/DevLog/UI/Common/Componeent/ToolbarButton+.swift index 12fce71..00c89da 100644 --- a/DevLog/UI/Common/Componeent/ToolbarButton+.swift +++ b/DevLog/UI/Common/Componeent/ToolbarButton+.swift @@ -29,6 +29,17 @@ struct ToolbarLeadingButton: ToolbarContent { struct ToolbarTrailingButton: ToolbarContent { var action: (() -> Void)? + private var isDisabled: Bool = false + + init(action: (() -> Void)? = nil) { + self.action = action + } + + func disabled(_ isDisabled: Bool) -> ToolbarTrailingButton { + var copy = self + copy.isDisabled = isDisabled + return copy + } var body: some ToolbarContent { ToolbarItem(placement: .topBarTrailing) { @@ -36,6 +47,7 @@ struct ToolbarTrailingButton: ToolbarContent { Button(role: .confirm) { action?() } + .disabled(isDisabled) } else { Button { action?() @@ -43,6 +55,7 @@ struct ToolbarTrailingButton: ToolbarContent { Text("확인") .bold() } + .disabled(isDisabled) } } } diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index e5f9505..b716611 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -47,14 +47,14 @@ struct TodoEditorView: View { .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.background, for: .navigationBar) .toolbar { - SheetToolbar( - onCancel: { dismiss() }, - onConfirm: { - onSubmit?(viewModel.upsertTodo()) - dismiss() - }, - isConfirmEnabled: viewModel.state.isValidToSave - ) + ToolbarLeadingButton { + dismiss() + } + ToolbarTrailingButton { + onSubmit?(viewModel.upsertTodo()) + dismiss() + } + .disabled(!viewModel.state.isValidToSave) } } } diff --git a/DevLog/UI/Setting/PushNotificationSettingsView.swift b/DevLog/UI/Setting/PushNotificationSettingsView.swift index 643ed3a..41e8c74 100644 --- a/DevLog/UI/Setting/PushNotificationSettingsView.swift +++ b/DevLog/UI/Setting/PushNotificationSettingsView.swift @@ -89,10 +89,12 @@ struct PushNotificationSettingsView: View { .onAppear { UIDatePicker.appearance().minuteInterval = 5 } .onDisappear { UIDatePicker.appearance().minuteInterval = 1 /* 기본값으로 복원 */ } .toolbar { - SheetToolbar( - onCancel: { viewModel.send(.rollbackUpdate) }, - onConfirm: { viewModel.send(.confirmUpdate) } - ) + ToolbarLeadingButton { + viewModel.send(.rollbackUpdate) + } + ToolbarTrailingButton { + viewModel.send(.confirmUpdate) + } } .background( GeometryReader { geometry in