From 3d5d2fd326bd8fb117fff3e4e91c41ec614a51c2 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 08:46:04 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=9B=B9=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=20HomeView=EC=97=90=EC=84=9C=20=EB=B3=BC?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20ui=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/HomeViewModel.swift | 1 + DevLog/UI/Home/HomeView.swift | 53 ++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index a81139b9..53626856 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -11,6 +11,7 @@ final class HomeViewModel: Store { struct State { var todoKindPreferences = TodoKind.allCases.map { TodoKindPreference(kind: $0, isVisible: true) } var pinnedTodos: [PinnedTodoItem] = [] + var webPages: [WebPageItem] = [] var showTodoKindPicker: Bool = false var showTodoEditor: Bool = false var showSearchView: Bool = false diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index cb1afa5c..51eab1d4 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -18,6 +18,7 @@ struct HomeView: View { List { todoSection pinnedSection + webPageSection } .listStyle(.insetGrouped) .navigationTitle("홈") @@ -38,6 +39,8 @@ struct HomeView: View { upsertUseCase: container.resolve(UpsertTodoUseCase.self), todoID: todoID )) + case .web(let url): + WebView(url: url) } } .toolbar { toolbar } @@ -201,8 +204,7 @@ struct HomeView: View { HStack { Text("중요 표시") .foregroundStyle(Color.primary) - .font(.title2) - .bold() + .font(.title2.bold()) Spacer() } @@ -210,6 +212,23 @@ struct HomeView: View { }) } + private var webPageSection: some View { + Section { + ForEach(viewModel.state.webPages, id: \.id) { page in + webResultRow(page) + } + } header: { + HStack { + Text("Web Page") + .foregroundStyle(Color.primary) + .font(.title2.bold()) + Spacer() + + } + .listRowInsets(EdgeInsets()) + } + } + @ToolbarContentBuilder private var toolbar: some ToolbarContent { ToolbarItem(placement: .topBarTrailing) { @@ -231,6 +250,35 @@ struct HomeView: View { } } + private func webResultRow(_ item: WebPageItem) -> some View { + Button { + router.push(Path.web(item.url)) + } label: { + HStack { + CacheableImage(url: item.imageURL) { + Image(systemName: "globe") + .resizable() + .scaledToFit() + } + .frame(width: sceneWidth / 10, height: sceneWidth / 10) + .clipShape(RoundedRectangle(cornerRadius: 10)) + + VStack(alignment: .leading) { + Text(item.title) + .foregroundStyle(Color.primary) + .bold() + .multilineTextAlignment(.leading) + .lineLimit(2) + Text(item.displayURL) + .foregroundStyle(Color.accentColor) + .underline() + } + Spacer() + } + .padding(.vertical, 4) + } + } + private var contentPicker: some View { NavigationStack { List { @@ -307,5 +355,6 @@ struct HomeView: View { private enum Path: Hashable { case kind(TodoKind) case detail(String) + case web(URL) } } From 87680c51c38e27b3b2958f353ca3e1e99c069696 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 09:15:39 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A6=88=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20ui=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/HomeViewModel.swift | 48 ++++++++++++++----- DevLog/Resource/Localizable.xcstrings | 5 +- DevLog/UI/Common/MainView.swift | 3 +- DevLog/UI/Home/HomeView.swift | 21 ++++++-- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index 53626856..02599c41 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -20,7 +20,8 @@ final class HomeViewModel: Store { var searchText: String = "" var isSearching: Bool = false var reorderTodo: Bool = false - var isLoading: Bool = false + var isPinnedLoading: Bool = false + var isWebPageLoading: Bool = false var showAlert: Bool = false var alertTitle: String = "" var alertType: AlertType? @@ -42,13 +43,16 @@ final class HomeViewModel: Store { case upsertTodo(Todo) case addWebPage case fetchPinnedTodos([PinnedTodoItem]) - case setLoading(Bool) + case fetchWebPages([WebPageItem]) + case setPinnedLoading(Bool) + case setWebPageLoading(Bool) } enum SideEffect { case upsertTodo(Todo) case addWebPage(String) case fetchPinnedTodos + case fetchWebPages } enum AlertType { @@ -60,16 +64,19 @@ final class HomeViewModel: Store { private let upsertTodoUseCase: UpsertTodoUseCase private let addWebPageUseCase: AddWebPageUseCase private let fetchPinnedTodosUseCase: FetchPinnedTodosUseCase + private let fetchWebPagesUseCase: FetchWebPagesUseCase @Published private(set) var state = State() init( addWebPageUseCase: AddWebPageUseCase, upsertTodoUseCase: UpsertTodoUseCase, - fetchPinnedTodosUseCase: FetchPinnedTodosUseCase + fetchPinnedTodosUseCase: FetchPinnedTodosUseCase, + fetchWebPagesUseCase: FetchWebPagesUseCase ) { self.addWebPageUseCase = addWebPageUseCase self.upsertTodoUseCase = upsertTodoUseCase self.fetchPinnedTodosUseCase = fetchPinnedTodosUseCase + self.fetchWebPagesUseCase = fetchWebPagesUseCase } func reduce(with action: Action) -> [SideEffect] { @@ -85,7 +92,7 @@ final class HomeViewModel: Store { case .onAppear, .updateSearching, .updateSearchText, .upsertTodo, .addWebPage: effects = reduceByView(action, state: &state) - case .fetchPinnedTodos, .setLoading: + case .fetchPinnedTodos, .fetchWebPages, .setPinnedLoading, .setWebPageLoading: effects = reduceByRun(action, state: &state) } @@ -98,8 +105,6 @@ final class HomeViewModel: Store { case .upsertTodo(let todo): Task { do { - defer { send(.setLoading(false)) } - send(.setLoading(true)) try await upsertTodoUseCase.execute(todo) } catch { send(.setAlert(isPresented: true, type: .error)) @@ -108,9 +113,11 @@ final class HomeViewModel: Store { case .addWebPage(let urlString): Task { do { - defer { send(.setLoading(false)) } - send(.setLoading(true)) + defer { send(.setWebPageLoading(false)) } + send(.setWebPageLoading(true)) _ = try await addWebPageUseCase.execute(urlString) + let pages = try await fetchWebPagesUseCase.execute("") + send(.fetchWebPages(pages.map { WebPageItem(from: $0) })) } catch { send(.setAlert(isPresented: true, type: .error)) } @@ -118,14 +125,25 @@ final class HomeViewModel: Store { case .fetchPinnedTodos: Task { do { - defer { send(.setLoading(false)) } - send(.setLoading(true)) + defer { send(.setPinnedLoading(false)) } + send(.setPinnedLoading(true)) let todos = try await fetchPinnedTodosUseCase.execute() send(.fetchPinnedTodos(todos.map { PinnedTodoItem(from: $0) })) } catch { send(.setAlert(isPresented: true, type: .error)) } } + case .fetchWebPages: + Task { + do { + defer { send(.setWebPageLoading(false)) } + send(.setWebPageLoading(true)) + let pages = try await fetchWebPagesUseCase.execute("") + send(.fetchWebPages(pages.map { WebPageItem(from: $0) })) + } catch { + send(.setAlert(isPresented: true, type: .error)) + } + } } } } @@ -162,7 +180,7 @@ private extension HomeViewModel { func reduceByView(_ action: Action, state: inout State) -> [SideEffect] { switch action { case .onAppear: - return [.fetchPinnedTodos] + return [.fetchPinnedTodos, .fetchWebPages] case .updateSearching(let isSearching): state.isSearching = isSearching case .updateSearchText(let text): @@ -186,8 +204,12 @@ private extension HomeViewModel { switch action { case .fetchPinnedTodos(let todos): state.pinnedTodos = todos - case .setLoading(let isLoading): - state.isLoading = isLoading + case .fetchWebPages(let pages): + state.webPages = pages + case .setPinnedLoading(let isLoading): + state.isPinnedLoading = isLoading + case .setWebPageLoading(let isLoading): + state.isWebPageLoading = isLoading default: break } diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 540c5813..816eb9d1 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -361,6 +361,9 @@ }, "작성된 내용이 없습니다." : { + }, + "저장한 Web Page가 표시됩니다." : { + }, "전체 삭제" : { @@ -386,7 +389,7 @@ "최근 검색" : { }, - "최근에 중요 표시를 한 Todo가 여기 표시됩니다." : { + "최근에 중요 표시를 한 Todo가 표시됩니다." : { }, "추가" : { diff --git a/DevLog/UI/Common/MainView.swift b/DevLog/UI/Common/MainView.swift index c4c259bf..448c91b7 100644 --- a/DevLog/UI/Common/MainView.swift +++ b/DevLog/UI/Common/MainView.swift @@ -15,7 +15,8 @@ struct MainView: View { HomeView(viewModel: HomeViewModel( addWebPageUseCase: container.resolve(AddWebPageUseCase.self), upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), - fetchPinnedTodosUseCase: container.resolve(FetchPinnedTodosUseCase.self) + fetchPinnedTodosUseCase: container.resolve(FetchPinnedTodosUseCase.self), + fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self) )) .tabItem { Image(systemName: "house.fill") diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index 51eab1d4..9780ebc7 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -163,12 +163,12 @@ struct HomeView: View { private var pinnedSection: some View { Section(content: { if viewModel.state.pinnedTodos.isEmpty { - if viewModel.state.isLoading { + if viewModel.state.isPinnedLoading { LoadingView(isClear: true) } else { HStack { Spacer() - Text("최근에 중요 표시를 한 Todo가 여기 표시됩니다.") + Text("최근에 중요 표시를 한 Todo가 표시됩니다.") .font(.callout) Spacer() } @@ -214,8 +214,21 @@ struct HomeView: View { private var webPageSection: some View { Section { - ForEach(viewModel.state.webPages, id: \.id) { page in - webResultRow(page) + if viewModel.state.webPages.isEmpty { + if viewModel.state.isWebPageLoading { + LoadingView(isClear: true) + } else { + HStack { + Spacer() + Text("저장한 Web Page가 표시됩니다.") + .font(.callout) + Spacer() + } + } + } else { + ForEach(viewModel.state.webPages, id: \.id) { page in + webResultRow(page) + } } } header: { HStack { From 3c7b6ac5837a4b0a2026918f96a57fdfc8e1583f Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 09:18:53 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Data/Repository/WebPageRepositoryImpl.swift | 9 +-------- DevLog/Domain/Protocol/WebPageRepository.swift | 2 +- .../UseCase/WebPage/Upsert/AddWebPageUseCase.swift | 2 +- .../UseCase/WebPage/Upsert/AddWebPageUseCaseImpl.swift | 2 +- DevLog/Presentation/ViewModel/HomeViewModel.swift | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/DevLog/Data/Repository/WebPageRepositoryImpl.swift b/DevLog/Data/Repository/WebPageRepositoryImpl.swift index 613e2da3..c962d0a0 100644 --- a/DevLog/Data/Repository/WebPageRepositoryImpl.swift +++ b/DevLog/Data/Repository/WebPageRepositoryImpl.swift @@ -23,7 +23,7 @@ final class WebPageRepositoryImpl: WebPageRepository { .compactMap { try? $0.toDomain() } } - func upsert(_ urlString: String) async throws -> WebPage { + func upsert(_ urlString: String) async throws { let metadata = try await metadataService.fetchMetadata(from: urlString) let request = WebPageRequest( title: metadata.title, @@ -32,13 +32,6 @@ final class WebPageRepositoryImpl: WebPageRepository { imageURL: metadata.imageURL ) try await webPageService.upsertWebPage(request) - let response = WebPageResponse( - title: request.title, - url: request.url, - displayURL: request.displayURL, - imageURL: request.imageURL - ) - return try response.toDomain() } func delete(_ urlString: String) async throws { diff --git a/DevLog/Domain/Protocol/WebPageRepository.swift b/DevLog/Domain/Protocol/WebPageRepository.swift index b3fd3174..3da6ad66 100644 --- a/DevLog/Domain/Protocol/WebPageRepository.swift +++ b/DevLog/Domain/Protocol/WebPageRepository.swift @@ -7,6 +7,6 @@ protocol WebPageRepository { func fetch(_ query: String) async throws -> [WebPage] - func upsert(_ urlString: String) async throws -> WebPage + func upsert(_ urlString: String) async throws func delete(_ urlString: String) async throws } diff --git a/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCase.swift b/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCase.swift index ab6aaab7..8f73c764 100644 --- a/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCase.swift +++ b/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCase.swift @@ -6,5 +6,5 @@ // protocol AddWebPageUseCase { - func execute(_ urlString: String) async throws -> WebPage + func execute(_ urlString: String) async throws } diff --git a/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCaseImpl.swift b/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCaseImpl.swift index 9aa3404e..8313a071 100644 --- a/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCaseImpl.swift +++ b/DevLog/Domain/UseCase/WebPage/Upsert/AddWebPageUseCaseImpl.swift @@ -12,7 +12,7 @@ final class AddWebPageUseCaseImpl: AddWebPageUseCase { self.repository = repository } - func execute(_ urlString: String) async throws -> WebPage { + func execute(_ urlString: String) async throws { try await repository.upsert(urlString) } } diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index 02599c41..529c4d1b 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -115,7 +115,7 @@ final class HomeViewModel: Store { do { defer { send(.setWebPageLoading(false)) } send(.setWebPageLoading(true)) - _ = try await addWebPageUseCase.execute(urlString) + try await addWebPageUseCase.execute(urlString) let pages = try await fetchWebPagesUseCase.execute("") send(.fetchWebPages(pages.map { WebPageItem(from: $0) })) } catch { From 008a48e62bfea534c8bc1042f89e2b252725df4e Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 09:34:24 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20LoadingView=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8A=A4=EC=99=80=EC=9D=B4=ED=94=84=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=9B=B9=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 1 + .../ViewModel/HomeViewModel.swift | 31 +++++++++++++++++-- DevLog/Resource/Localizable.xcstrings | 3 ++ DevLog/UI/Common/MainView.swift | 1 + DevLog/UI/Home/HomeView.swift | 12 +++++++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index fb904f64..6767d1a4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,3 +2,4 @@ disabled_rules: - nesting - multiple_closures_with_trailing_closure - trailing_whitespace + - type_body_length diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index 529c4d1b..91bc0041 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -22,6 +22,7 @@ final class HomeViewModel: Store { var reorderTodo: Bool = false var isPinnedLoading: Bool = false var isWebPageLoading: Bool = false + var isWebPageInputLoading: Bool = false var showAlert: Bool = false var alertTitle: String = "" var alertType: AlertType? @@ -42,15 +43,18 @@ final class HomeViewModel: Store { case updateSearchText(String) case upsertTodo(Todo) case addWebPage + case deleteWebPage(WebPageItem) case fetchPinnedTodos([PinnedTodoItem]) case fetchWebPages([WebPageItem]) case setPinnedLoading(Bool) case setWebPageLoading(Bool) + case setWebPageInputLoading(Bool) } enum SideEffect { case upsertTodo(Todo) case addWebPage(String) + case deleteWebPage(String) case fetchPinnedTodos case fetchWebPages } @@ -63,17 +67,20 @@ final class HomeViewModel: Store { private let upsertTodoUseCase: UpsertTodoUseCase private let addWebPageUseCase: AddWebPageUseCase + private let deleteWebPageUseCase: DeleteWebPageUseCase private let fetchPinnedTodosUseCase: FetchPinnedTodosUseCase private let fetchWebPagesUseCase: FetchWebPagesUseCase @Published private(set) var state = State() init( addWebPageUseCase: AddWebPageUseCase, + deleteWebPageUseCase: DeleteWebPageUseCase, upsertTodoUseCase: UpsertTodoUseCase, fetchPinnedTodosUseCase: FetchPinnedTodosUseCase, fetchWebPagesUseCase: FetchWebPagesUseCase ) { self.addWebPageUseCase = addWebPageUseCase + self.deleteWebPageUseCase = deleteWebPageUseCase self.upsertTodoUseCase = upsertTodoUseCase self.fetchPinnedTodosUseCase = fetchPinnedTodosUseCase self.fetchWebPagesUseCase = fetchWebPagesUseCase @@ -89,10 +96,10 @@ final class HomeViewModel: Store { .updateWebPageURLInput, .setAlert: effects = reduceByUser(action, state: &state) - case .onAppear, .updateSearching, .updateSearchText, .upsertTodo, .addWebPage: + case .onAppear, .updateSearching, .updateSearchText, .upsertTodo, .addWebPage, .deleteWebPage: effects = reduceByView(action, state: &state) - case .fetchPinnedTodos, .fetchWebPages, .setPinnedLoading, .setWebPageLoading: + case .fetchPinnedTodos, .fetchWebPages, .setPinnedLoading, .setWebPageLoading, .setWebPageInputLoading: effects = reduceByRun(action, state: &state) } @@ -111,14 +118,28 @@ final class HomeViewModel: Store { } } case .addWebPage(let urlString): + Task { + do { + defer { send(.setWebPageInputLoading(false)) } + send(.setWebPageInputLoading(true)) + try await addWebPageUseCase.execute(urlString) + let pages = try await fetchWebPagesUseCase.execute("") + send(.fetchWebPages(pages.map { WebPageItem(from: $0) })) + } catch { + send(.setWebPageInputLoading(false)) + send(.setAlert(isPresented: true, type: .error)) + } + } + case .deleteWebPage(let urlString): Task { do { defer { send(.setWebPageLoading(false)) } send(.setWebPageLoading(true)) - try await addWebPageUseCase.execute(urlString) + try await deleteWebPageUseCase.execute(urlString) let pages = try await fetchWebPagesUseCase.execute("") send(.fetchWebPages(pages.map { WebPageItem(from: $0) })) } catch { + send(.setWebPageLoading(false)) send(.setAlert(isPresented: true, type: .error)) } } @@ -194,6 +215,8 @@ private extension HomeViewModel { } setAlert(&state, isPresented: false, type: nil) return [.addWebPage(normalizedURL)] + case .deleteWebPage(let page): + return [.deleteWebPage(page.url.absoluteString)] default: break } @@ -210,6 +233,8 @@ private extension HomeViewModel { state.isPinnedLoading = isLoading case .setWebPageLoading(let isLoading): state.isWebPageLoading = isLoading + case .setWebPageInputLoading(let isLoading): + state.isWebPageInputLoading = isLoading default: break } diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 816eb9d1..fc120f3a 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -313,6 +313,9 @@ }, "사용자 설정" : { + }, + "삭제" : { + }, "상태 설정" : { diff --git a/DevLog/UI/Common/MainView.swift b/DevLog/UI/Common/MainView.swift index 448c91b7..66b8be49 100644 --- a/DevLog/UI/Common/MainView.swift +++ b/DevLog/UI/Common/MainView.swift @@ -14,6 +14,7 @@ struct MainView: View { TabView { HomeView(viewModel: HomeViewModel( addWebPageUseCase: container.resolve(AddWebPageUseCase.self), + deleteWebPageUseCase: container.resolve(DeleteWebPageUseCase.self), upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), fetchPinnedTodosUseCase: container.resolve(FetchPinnedTodosUseCase.self), fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self) diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index 9780ebc7..fe519c31 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -98,6 +98,11 @@ struct HomeView: View { .onAppear { viewModel.send(.onAppear) } + .overlay { + if viewModel.state.isWebPageInputLoading { + LoadingView() + } + } } } @@ -290,6 +295,13 @@ struct HomeView: View { } .padding(.vertical, 4) } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button(role: .destructive) { + viewModel.send(.deleteWebPage(item)) + } label: { + Label("삭제", systemImage: "trash") + } + } } private var contentPicker: some View { From b3474c84a7851440ed1dbc1f9c38d4b96eac6022 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 09:42:28 +0900 Subject: [PATCH 5/6] =?UTF-8?q?ui:=20push=20->=20navigationlink,=20title?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/HomeView.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index fe519c31..5415474d 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -39,8 +39,14 @@ struct HomeView: View { upsertUseCase: container.resolve(UpsertTodoUseCase.self), todoID: todoID )) - case .web(let url): - WebView(url: url) + case .web(let page): + WebView(url: page.url) + .toolbar { + ToolbarItem(placement: .principal) { + Text(page.title) + .bold() + } + } } } .toolbar { toolbar } @@ -269,9 +275,7 @@ struct HomeView: View { } private func webResultRow(_ item: WebPageItem) -> some View { - Button { - router.push(Path.web(item.url)) - } label: { + NavigationLink(value: Path.web(item)) { HStack { CacheableImage(url: item.imageURL) { Image(systemName: "globe") @@ -380,6 +384,6 @@ struct HomeView: View { private enum Path: Hashable { case kind(TodoKind) case detail(String) - case web(URL) + case web(WebPageItem) } } From 9365b411bfd5a2d226dadaddb6f7988aa08c6743 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 23 Feb 2026 09:49:22 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=ED=99=94=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Presentation/ViewModel/SearchViewModel.swift | 4 ---- DevLog/UI/Search/SearchView.swift | 13 +++++-------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/DevLog/Presentation/ViewModel/SearchViewModel.swift b/DevLog/Presentation/ViewModel/SearchViewModel.swift index b8ec927d..41d0fc51 100644 --- a/DevLog/Presentation/ViewModel/SearchViewModel.swift +++ b/DevLog/Presentation/ViewModel/SearchViewModel.swift @@ -13,7 +13,6 @@ final class SearchViewModel: Store { var isLoading: Bool = false var isSearching: Bool = false var searchQuery: String = "" - var selectedWebPage: WebPageItem? var webPages: [WebPageItem] = [] var todos: [TodoListItem] = [] var recentQueries: OrderedSet = [] @@ -27,7 +26,6 @@ final class SearchViewModel: Store { enum Action { case fetchWebPage([WebPageItem]) case fetchTodos([TodoListItem]) - case selectWebPage(WebPageItem) case addRecentQuery(String) case removeRecentQuery(String) case clearRecentQueries @@ -78,8 +76,6 @@ final class SearchViewModel: Store { state.webPages = items case .fetchTodos(let items): state.todos = items - case .selectWebPage(let item): - state.selectedWebPage = item case .addRecentQuery(let query): let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { break } diff --git a/DevLog/UI/Search/SearchView.swift b/DevLog/UI/Search/SearchView.swift index 541ef134..3082d9e5 100644 --- a/DevLog/UI/Search/SearchView.swift +++ b/DevLog/UI/Search/SearchView.swift @@ -25,11 +25,11 @@ struct SearchView: View { upsertUseCase: container.resolve(UpsertTodoUseCase.self), todoID: todoID )) - case .web(let url): - WebView(url: url) + case .web(let page): + WebView(url: page.url) .toolbar { ToolbarItem(placement: .principal) { - Text(viewModel.state.selectedWebPage?.title ?? "") + Text(page.title) .bold() } } @@ -213,10 +213,7 @@ struct SearchView: View { } private func webResultRow(_ item: WebPageItem) -> some View { - Button { - viewModel.send(.selectWebPage(item)) - router.push(Path.web(item.url)) - } label: { + NavigationLink(value: Path.web(item)) { VStack(spacing: 4) { HStack { CacheableImage(url: item.imageURL) { @@ -290,6 +287,6 @@ struct SearchView: View { private enum Path: Hashable { case todo(String) - case web(URL) + case web(WebPageItem) } }