From 3cf0fce2a42ff3a1253eb112df672775dff1580d Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 4 Oct 2026 21:58:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?ui:=20Tag=EA=B0=80=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=ED=8F=AD=EC=9D=84=20=EB=84=98?= =?UTF-8?q?=EC=9C=BC=EB=A9=B4=20=EB=A7=90=EC=A4=84=EC=9E=84=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Common/Component/Tags.swift | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Application/Presentation/PresentationShared/Sources/Common/Component/Tags.swift b/Application/Presentation/PresentationShared/Sources/Common/Component/Tags.swift index 55459c84..747df580 100644 --- a/Application/Presentation/PresentationShared/Sources/Common/Component/Tags.swift +++ b/Application/Presentation/PresentationShared/Sources/Common/Component/Tags.swift @@ -46,7 +46,6 @@ public struct Tag: View { .foregroundStyle(Color.accent) .bold() .lineLimit(1) - .fixedSize() .padding(.vertical, 4) .padding(.leading, 8) .padding(.trailing, isEditing ? 0 : 8) @@ -245,7 +244,7 @@ private struct TagLayout: Layout { var minX = bounds.minX for index in row.indices { - let size = subviews[index].sizeThatFits(.unspecified) + let size = fittedSize(of: subviews[index], maxWidth: width) subviews[index].place( at: CGPoint(x: minX, y: minY), proposal: ProposedViewSize(size) @@ -292,7 +291,7 @@ private struct TagLayout: Layout { for index in contentIndices { let subview = subviews[index] - let size = subview.sizeThatFits(.unspecified) + let size = fittedSize(of: subview, maxWidth: availableWidth) if currentWidth + size.width > availableWidth && !currentRow.indices.isEmpty { rows.append(currentRow) @@ -330,7 +329,7 @@ private struct TagLayout: Layout { } let overflowIndex = subviews.count - 1 - let overflowSize = subviews[overflowIndex].sizeThatFits(.unspecified) + let overflowSize = fittedSize(of: subviews[overflowIndex], maxWidth: maxWidth) guard !limited.isEmpty else { return [Row(indices: [overflowIndex], maxHeight: overflowSize.height, width: overflowSize.width)] } @@ -341,7 +340,7 @@ private struct TagLayout: Layout { while !candidateIndices.isEmpty { let rowIndices = candidateIndices + [overflowIndex] - let rowWidth = width(for: rowIndices, subviews: subviews) + let rowWidth = width(for: rowIndices, subviews: subviews, maxWidth: maxWidth) if rowWidth <= maxWidth { lastRow.indices = rowIndices lastRow.maxHeight = max(lastRow.maxHeight, overflowSize.height) @@ -360,15 +359,25 @@ private struct TagLayout: Layout { private func width( for indices: [Int], - subviews: Subviews + subviews: Subviews, + maxWidth: CGFloat ) -> CGFloat { guard !indices.isEmpty else { return 0 } let widths = indices.reduce(CGFloat.zero) { partialResult, index in - partialResult + subviews[index].sizeThatFits(.unspecified).width + partialResult + fittedSize(of: subviews[index], maxWidth: maxWidth).width } return widths + CGFloat(max(0, indices.count - 1)) * horizontalSpacing } + private func fittedSize( + of subview: LayoutSubview, + maxWidth: CGFloat + ) -> CGSize { + subview.sizeThatFits( + ProposedViewSize(width: maxWidth.isFinite ? maxWidth : nil, height: nil) + ) + } + private var usesOverflowIndicator: Bool { showsOverflowIndicator && (lineLimit ?? 0) > 0 } From ed01752b7b7a24d5fbe7041f4fdc0365c81171eb Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 4 Oct 2026 21:58:41 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20TodoInfoSheetView=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Common/TodoInfoSheetView.swift | 105 ------------------ 1 file changed, 105 deletions(-) delete mode 100644 Application/Presentation/PresentationShared/Sources/Common/TodoInfoSheetView.swift diff --git a/Application/Presentation/PresentationShared/Sources/Common/TodoInfoSheetView.swift b/Application/Presentation/PresentationShared/Sources/Common/TodoInfoSheetView.swift deleted file mode 100644 index 8063a712..00000000 --- a/Application/Presentation/PresentationShared/Sources/Common/TodoInfoSheetView.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// TodoInfoSheetView.swift -// PresentationShared -// -// Created by 최윤진 on 3/2/26. -// - -import SwiftUI -import Domain - -struct TodoInfoSheetView: View { - let createdAt: Date - let completedAt: Date? - let dueDate: Date? - let tags: [String] - let onClose: () -> Void - - var body: some View { - NavigationStack { - ScrollView { - LazyVStack(spacing: 32) { - VStack(alignment: .leading) { - Text(String(localized: "todo_info_created_at", bundle: PresentationResources.bundle)) - .font(.subheadline) - .foregroundStyle(.white) - HStack(spacing: 8) { - Image(systemName: "calendar") - Text(createdAt.formatted(date: .abbreviated, time: .omitted)) - Spacer() - } - .foregroundStyle(.white) - .padding(.vertical, 10) - .padding(.horizontal, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(.blue) - ) - Divider() - } - VStack(alignment: .leading) { - Text(String(localized: "todo_info_due_date", bundle: PresentationResources.bundle)) - .font(.subheadline) - .foregroundStyle(.secondary) - HStack(spacing: 8) { - Image(systemName: "calendar") - .foregroundStyle(.secondary) - Text( - dueDate? - .formatted(date: .abbreviated, time: .omitted) - ?? String(localized: "todo_info_no_due_date", bundle: PresentationResources.bundle) - ) - .foregroundStyle(dueDate == nil ? .secondary : .primary) - Spacer() - } - .padding(.vertical, 10) - .padding(.horizontal, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(Color(.tertiarySystemFill)) - ) - Divider() - } - VStack(alignment: .leading) { - Text(String(localized: "todo_info_completed_at", bundle: PresentationResources.bundle)) - .font(.subheadline) - .foregroundStyle(.secondary) - HStack(spacing: 8) { - Image(systemName: "calendar") - .foregroundStyle(.secondary) - Text( - completedAt? - .formatted(date: .abbreviated, time: .omitted) - ?? String(localized: "todo_info_not_completed", bundle: PresentationResources.bundle) - ) - .foregroundStyle(.white) - Spacer() - } - .padding(.vertical, 10) - .padding(.horizontal, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(.green) - ) - Divider() - } - VStack(alignment: .leading) { - Text(String(localized: "todo_tags", bundle: PresentationResources.bundle)) - .font(.subheadline) - .foregroundStyle(.secondary) - Divider() - if !tags.isEmpty { - TagList(tags) - } - } - } - .padding(.horizontal) - } - .toolbar { - ToolbarLeadingButton { - onClose() - } - } - } - } -} From be2a0064d50f990030ecfd491c8c35ac369b478a Mon Sep 17 00:00:00 2001 From: opficdev Date: Sun, 4 Oct 2026 21:58:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?ui:=20Todo=20=EC=84=B8=EB=B6=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=8B=9C=ED=8A=B8=20=EC=B9=B4=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Todo/Detail/TodoDetailView.swift | 136 ++++++++++++------ 1 file changed, 95 insertions(+), 41 deletions(-) diff --git a/Application/Presentation/PresentationShared/Sources/Todo/Detail/TodoDetailView.swift b/Application/Presentation/PresentationShared/Sources/Todo/Detail/TodoDetailView.swift index a54d7595..5078072d 100644 --- a/Application/Presentation/PresentationShared/Sources/Todo/Detail/TodoDetailView.swift +++ b/Application/Presentation/PresentationShared/Sources/Todo/Detail/TodoDetailView.swift @@ -181,77 +181,131 @@ private struct InfoSheetView: View { private let calendar = Calendar.current var body: some View { - NavigationStack { - List { - Section(String(localized: "todo_options_section", bundle: PresentationResources.bundle)) { - HStack { - Text(String(localized: "todo_category", bundle: PresentationResources.bundle)) - Spacer() + ScrollView { + LazyVStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 0) { + optionRow( + systemImage: "tag.fill", + color: Color.accent, + title: String(localized: "todo_category", bundle: PresentationResources.bundle) + ) { Text(TodoCategoryItem(from: todo.category).localizedName) - .foregroundStyle(.secondary) + .foregroundStyle(Color.accent) + .lineLimit(1) } - statusRow( - title: String(localized: "todo_completed", bundle: PresentationResources.bundle), - systemImage: todo.isCompleted ? "checkmark.circle.fill" : "circle", - color: todo.isCompleted ? .green : .secondary - ) + Divider() - statusRow( - title: String(localized: "todo_pinned", bundle: PresentationResources.bundle), - systemImage: todo.isPinned ? "star.fill" : "star", - color: todo.isPinned ? .orange : .secondary - ) + optionRow( + systemImage: "circle", + color: Color.textSecondary, + title: String(localized: "todo_completed", bundle: PresentationResources.bundle) + ) { + Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle") + .foregroundStyle(todo.isCompleted ? Color.accent : Color.textTertiary) + } + + Divider() - HStack { - Text(String(localized: "todo_due_date", bundle: PresentationResources.bundle)) + optionRow( + systemImage: "star.fill", + color: Color.orange, + title: String(localized: "todo_pinned", bundle: PresentationResources.bundle) + ) { + Image(systemName: todo.isPinned ? "star.fill" : "star") + .foregroundStyle(todo.isPinned ? Color.orange : Color.textTertiary) + } - Spacer() + Divider() + optionRow( + systemImage: "calendar", + color: Color.textSecondary, + title: String(localized: "todo_due_date", bundle: PresentationResources.bundle) + ) { if let dueDate = todo.dueDate { Tag(dueDateText(for: dueDate), isEditing: false) - .padding(.vertical, -4) } else { Text(String(localized: "todo_none", bundle: PresentationResources.bundle)) - .foregroundStyle(.secondary) + .foregroundStyle(Color.textSecondary) } } } + .background(Color.surface) + .clipShape(.rect(cornerRadius: 16)) - Section(String(localized: "todo_tags", bundle: PresentationResources.bundle)) { - if todo.tags.isEmpty { - Text(String(localized: "todo_no_tags", bundle: PresentationResources.bundle)) - .foregroundStyle(.secondary) - .padding(.vertical, 4) - } else { - TagList(todo.tags) + VStack(alignment: .leading, spacing: 12) { + Text(String(localized: "todo_tags", bundle: PresentationResources.bundle)) + .font(.headline) + .padding(.horizontal, 16) + + Group { + if todo.tags.isEmpty { + Text(String(localized: "todo_no_tags", bundle: PresentationResources.bundle)) + .foregroundStyle(Color.textSecondary) + } else { + TagList(todo.tags, verticalSpacing: 4, horizontalSpacing: 4) + } } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.surface) + .clipShape(.rect(cornerRadius: 16)) } } - .navigationTitle(String(localized: "todo_details", bundle: PresentationResources.bundle)) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarLeadingButton { + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .safeAreaInset(edge: .top, spacing: 0) { topBar } + .background(Color.appBackground.ignoresSafeArea()) + .presentationDragIndicator(.visible) + } + + private var topBar: some View { + ZStack { + Text(String(localized: "todo_details", bundle: PresentationResources.bundle)) + .font(.headline) + HStack { + Button { onClose() + } label: { + if #available(iOS 26.0, *) { + Image(systemName: "xmark") + } else { + Text(String(localized: "common_close", bundle: PresentationResources.bundle)) + } } + .topBarButtonStyle() + Spacer() } } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.appBackground, ignoresSafeAreaEdges: .top) } - @ViewBuilder - private func statusRow( - title: String, + private func optionRow( systemImage: String, - color: Color + color: Color, + title: String, + @ViewBuilder trailing: () -> Trailing ) -> some View { - HStack { + HStack(spacing: 12) { + Image(systemName: systemImage) + .font(.title3) + .foregroundStyle(color) + .frame(width: 28, height: 28) + Text(title) + .font(.headline) + .layoutPriority(1) - Spacer() + Spacer(minLength: 8) - Image(systemName: systemImage) - .foregroundStyle(color) + trailing() } + .padding(.horizontal, 16) + .padding(.vertical, 12) } private func dueDateText(for dueDate: Date) -> String {