From 61ebda50e9ad8f5ccad8e589215873dcec7f836d Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 11:34:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?ui:=20=EC=8A=A4=ED=8B=B0=ED=82=A4=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=EB=A1=9C=20=EC=A0=95=EB=A0=AC,=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EC=98=B5=EC=85=98=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EC=8B=9C=ED=82=A8=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Data/DTO/TodoCursorDTO.swift | 2 +- DevLog/Data/Mapper/TodoMapping.swift | 4 +- DevLog/Domain/Entity/TodoCursor.swift | 2 +- DevLog/Domain/Entity/TodoQuery.swift | 66 +++- .../Fetch/Todo/FetchTodosByKindUseCase.swift | 2 +- .../Todo/FetchTodosByKindUseCaseImpl.swift | 3 +- DevLog/Infra/Service/TodoService.swift | 32 +- .../Presentation/Structure/TodoListItem.swift | 4 + .../ViewModel/TodoListViewModel.swift | 97 +++++- DevLog/Resource/Localizable.xcstrings | 33 +- DevLog/UI/Home/TodoListView.swift | 319 +++++++++++------- 11 files changed, 385 insertions(+), 179 deletions(-) diff --git a/DevLog/Data/DTO/TodoCursorDTO.swift b/DevLog/Data/DTO/TodoCursorDTO.swift index bf99f89..09abf1f 100644 --- a/DevLog/Data/DTO/TodoCursorDTO.swift +++ b/DevLog/Data/DTO/TodoCursorDTO.swift @@ -8,6 +8,6 @@ import Foundation struct TodoCursorDTO { - let createdAt: Date + let orderedAt: Date let documentID: String } diff --git a/DevLog/Data/Mapper/TodoMapping.swift b/DevLog/Data/Mapper/TodoMapping.swift index 1b5888b..e724462 100644 --- a/DevLog/Data/Mapper/TodoMapping.swift +++ b/DevLog/Data/Mapper/TodoMapping.swift @@ -50,14 +50,14 @@ extension TodoResponse { extension TodoCursorDTO { func toDomain() -> TodoCursor { TodoCursor( - createdAt: createdAt, + orderedAt: orderedAt, documentID: documentID ) } static func fromDomain(_ cursor: TodoCursor) -> Self { TodoCursorDTO( - createdAt: cursor.createdAt, + orderedAt: cursor.orderedAt, documentID: cursor.documentID ) } diff --git a/DevLog/Domain/Entity/TodoCursor.swift b/DevLog/Domain/Entity/TodoCursor.swift index 2e826cd..552cd98 100644 --- a/DevLog/Domain/Entity/TodoCursor.swift +++ b/DevLog/Domain/Entity/TodoCursor.swift @@ -8,6 +8,6 @@ import Foundation struct TodoCursor { - let createdAt: Date + let orderedAt: Date let documentID: String } diff --git a/DevLog/Domain/Entity/TodoQuery.swift b/DevLog/Domain/Entity/TodoQuery.swift index 934be89..5b2e80b 100644 --- a/DevLog/Domain/Entity/TodoQuery.swift +++ b/DevLog/Domain/Entity/TodoQuery.swift @@ -8,31 +8,77 @@ import Foundation struct TodoQuery { - let kind: TodoKind? - let keyword: String? - let isPinned: Bool? - let createdAtFrom: Date? - let createdAtTo: Date? - let createdAtDescending: Bool - let pageSize: Int - let fetchAllPages: Bool + enum SortTarget: Equatable, Hashable { + case createdAt + case updatedAt + + var fieldName: String { + switch self { + case .createdAt: + return "createdAt" + case .updatedAt: + return "updatedAt" + } + } + } + + enum SortOrder: Equatable, Hashable { + case latest + case oldest + + var isDescending: Bool { + self == .latest + } + } + + enum CompletionFilter: Equatable, Hashable { + case all + case incomplete + case completed + + var isCompletedValue: Bool? { + switch self { + case .all: + return nil + case .incomplete: + return false + case .completed: + return true + } + } + } + + var kind: TodoKind? + var keyword: String? + var isPinned: Bool? + var completionFilter: CompletionFilter + var createdAtFrom: Date? + var createdAtTo: Date? + var sortTarget: SortTarget + var sortOrder: SortOrder + var pageSize: Int + var fetchAllPages: Bool init( kind: TodoKind? = nil, keyword: String? = nil, isPinned: Bool? = nil, + completionFilter: CompletionFilter = .all, createdAtFrom: Date? = nil, createdAtTo: Date? = nil, - createdAtDescending: Bool = true, + sortTarget: SortTarget = .createdAt, + sortOrder: SortOrder = .latest, pageSize: Int = 20, fetchAllPages: Bool = false ) { self.kind = kind self.keyword = keyword self.isPinned = isPinned + self.completionFilter = completionFilter self.createdAtFrom = createdAtFrom self.createdAtTo = createdAtTo - self.createdAtDescending = createdAtDescending + self.sortTarget = sortTarget + self.sortOrder = sortOrder self.pageSize = pageSize self.fetchAllPages = fetchAllPages } diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift index c478a54..f161362 100644 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift +++ b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift @@ -6,5 +6,5 @@ // protocol FetchTodosByKindUseCase { - func execute(_ kind: TodoKind, cursor: TodoCursor?) async throws -> TodoPage + func execute(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage } diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift index b8680f9..bfe754b 100644 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift +++ b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift @@ -12,8 +12,7 @@ final class FetchTodosByKindUseCaseImpl: FetchTodosByKindUseCase { self.repository = repository } - func execute(_ kind: TodoKind, cursor: TodoCursor?) async throws -> TodoPage { - let query = TodoQuery(kind: kind) + func execute(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage { return try await repository.fetchTodos(query, cursor: cursor) } } diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index 3ec1044..f6df99b 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -21,10 +21,12 @@ final class TodoService { let trimmedKeyword = query.keyword?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let logComponents: [String?] = [ - "createdAtDescending=\(query.createdAtDescending)", + "sortTarget=\(query.sortTarget.fieldName)", + "sortOrder=\(query.sortOrder == .latest ? "latest" : "oldest")", query.keyword != nil ? "keywordLength=\(trimmedKeyword.count)" : nil, query.kind != nil ? "kind=\(query.kind!.rawValue)" : nil, query.isPinned != nil ? "pinned=\(query.isPinned!)" : nil, + query.completionFilter.isCompletedValue != nil ? "completed=\(query.completionFilter.isCompletedValue!)" : nil, query.createdAtFrom != nil ? "createdAtFrom=\(query.createdAtFrom!)" : nil, query.createdAtTo != nil ? "createdAtTo=\(query.createdAtTo!)" : nil, "pageSize=\(query.pageSize)", @@ -35,7 +37,7 @@ final class TodoService { var firestoreQuery: Query = store .collection("users/\(uid)/todoLists/") - .order(by: "createdAt", descending: query.createdAtDescending) + .order(by: query.sortTarget.fieldName, descending: query.sortOrder.isDescending) .order(by: FieldPath.documentID()) if let kind = query.kind { @@ -46,6 +48,10 @@ final class TodoService { firestoreQuery = firestoreQuery.whereField("isPinned", isEqualTo: isPinned) } + if let isCompleted = query.completionFilter.isCompletedValue { + firestoreQuery = firestoreQuery.whereField("isCompleted", isEqualTo: isCompleted) + } + if let createdAtFrom = query.createdAtFrom { firestoreQuery = firestoreQuery.whereField( "createdAt", @@ -69,7 +75,7 @@ final class TodoService { var pageQuery = firestoreQuery if let pageCursor { pageQuery = pageQuery.start(after: [ - Timestamp(date: pageCursor.createdAt), + Timestamp(date: pageCursor.orderedAt), pageCursor.documentID ]) } @@ -83,7 +89,10 @@ final class TodoService { } guard let lastDocument = snapshot.documents.last, - let nextCursor = makeCursor(from: lastDocument) else { + let nextCursor = makeCursor( + from: lastDocument, + orderField: query.sortTarget.fieldName + ) else { break } @@ -95,7 +104,7 @@ final class TodoService { if let cursor { firestoreQuery = firestoreQuery.start(after: [ - Timestamp(date: cursor.createdAt), + Timestamp(date: cursor.orderedAt), cursor.documentID ]) } @@ -103,7 +112,9 @@ final class TodoService { firestoreQuery = firestoreQuery.limit(to: query.pageSize) let snapshot = try await firestoreQuery.getDocuments() let items = snapshot.documents.compactMap { makeResponse(from: $0) } - let nextCursor = snapshot.documents.last.flatMap { makeCursor(from: $0) } + let nextCursor = snapshot.documents.last.flatMap { + makeCursor(from: $0, orderField: query.sortTarget.fieldName) + } return TodoPageResponse(items: items, nextCursor: nextCursor) } @@ -184,13 +195,16 @@ final class TodoService { } private extension TodoService { - func makeCursor(from document: QueryDocumentSnapshot) -> TodoCursorDTO? { - guard let createdAt = document.data()[TodoFieldKey.createdAt.rawValue] as? Timestamp else { + func makeCursor( + from document: QueryDocumentSnapshot, + orderField: String + ) -> TodoCursorDTO? { + guard let orderedAt = document.data()[orderField] as? Timestamp else { return nil } return TodoCursorDTO( - createdAt: createdAt.dateValue(), + orderedAt: orderedAt.dateValue(), documentID: document.documentID ) } diff --git a/DevLog/Presentation/Structure/TodoListItem.swift b/DevLog/Presentation/Structure/TodoListItem.swift index 9bfc1eb..97245d5 100644 --- a/DevLog/Presentation/Structure/TodoListItem.swift +++ b/DevLog/Presentation/Structure/TodoListItem.swift @@ -13,6 +13,8 @@ struct TodoListItem: Identifiable, Hashable { let tags: [String] let isPinned: Bool let isCompleted: Bool + let createdAt: Date + let updatedAt: Date init(from todo: Todo) { self.id = todo.id @@ -20,5 +22,7 @@ struct TodoListItem: Identifiable, Hashable { self.tags = todo.tags self.isPinned = todo.isPinned self.isCompleted = todo.isCompleted + self.createdAt = todo.createdAt + self.updatedAt = todo.updatedAt } } diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index a565364..023c0f5 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -17,7 +17,7 @@ final class TodoListViewModel: Store { var alertTitle: String = "" var alertMessage: String = "" var scope: TodoScope = .title - var filterOption: FilterOption = .create + var query: TodoQuery var isLoading: Bool = false var showToast: Bool = false var toastMessage: String = "" @@ -26,17 +26,17 @@ final class TodoListViewModel: Store { var pendingTask: (TodoListItem, Int)? } - enum FilterOption { - case create, update, day, week, month, year - } - enum Action { // User case refresh case setAlert(Bool) case setShowEditor(Bool) case swipeTodo(TodoListItem) - case tapFilterOption(FilterOption) + case setSortTarget(TodoQuery.SortTarget) + case setSortOrder(TodoQuery.SortOrder) + case togglePinnedOnly + case setCompletionFilter(TodoQuery.CompletionFilter) + case resetFilters case tapToggleCompleted(TodoListItem) case tapTogglePinned(TodoListItem) case undoDelete @@ -73,7 +73,6 @@ final class TodoListViewModel: Store { private let fetchTodoByIDUseCase: FetchTodoByIDUseCase private let upsertTodoUseCase: UpsertTodoUseCase private let deleteTodoUseCase: DeleteTodoUseCase - private let pageSize = 20 init( fetchTodosByKindUseCase: FetchTodosByKindUseCase, @@ -86,7 +85,19 @@ final class TodoListViewModel: Store { self.fetchTodoByIDUseCase = fetchTodoByIDUseCase self.upsertTodoUseCase = upsertTodoUseCase self.deleteTodoUseCase = deleteTodoUseCase - self.state = State(kind: kind) + self.state = State( + kind: kind, + query: TodoQuery(kind: kind) + ) + } + + var appliedFilterCount: Int { + var count = 0 + if state.query.sortTarget != .createdAt { count += 1 } + if state.query.sortOrder != .latest { count += 1 } + if state.query.isPinned != nil { count += 1 } + if state.query.completionFilter != .all { count += 1 } + return count } func reduce(with action: Action) -> [SideEffect] { @@ -94,7 +105,9 @@ final class TodoListViewModel: Store { var effects: [SideEffect] = [] switch action { - case .refresh, .setAlert, .setShowEditor, .swipeTodo, .tapFilterOption, .tapToggleCompleted, .tapTogglePinned, .undoDelete: + case .refresh, .setAlert, .setShowEditor, .swipeTodo, .setSortTarget, .setSortOrder, + .togglePinnedOnly, .setCompletionFilter, .resetFilters, .tapToggleCompleted, + .tapTogglePinned, .undoDelete: effects = reduceByUser(action, state: &state) case .confirmDelete, .onAppear, .loadNextPage, .setScope, .setSearchText, .setToast, .upsertTodo: @@ -115,10 +128,10 @@ final class TodoListViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let page = try await fetchTodosByKindUseCase.execute(state.kind, cursor: nil) + let page = try await fetchTodosByKindUseCase.execute(state.query, cursor: nil) send(.resetPagination) send(.appendTodos(page.items.map { TodoListItem(from: $0) }, nextCursor: page.nextCursor)) - let hasMore = page.items.count == pageSize && page.nextCursor != nil + let hasMore = page.items.count == state.query.pageSize && page.nextCursor != nil send(.setHasMore(hasMore)) } catch { send(.setAlert(true)) @@ -129,9 +142,9 @@ final class TodoListViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let page = try await fetchTodosByKindUseCase.execute(state.kind, cursor: state.nextCursor) + let page = try await fetchTodosByKindUseCase.execute(state.query, cursor: state.nextCursor) send(.appendTodos(page.items.map { TodoListItem(from: $0) }, nextCursor: page.nextCursor)) - let hasMore = page.items.count == pageSize && page.nextCursor != nil + let hasMore = page.items.count == state.query.pageSize && page.nextCursor != nil send(.setHasMore(hasMore)) } catch { send(.setAlert(true)) @@ -171,6 +184,7 @@ final class TodoListViewModel: Store { send(.setLoading(true)) var todo = try await fetchTodoByIDUseCase.execute(item.id) todo.isPinned.toggle() + todo.updatedAt = Date() try await upsertTodoUseCase.execute(todo) send(.didTogglePinned(TodoListItem(from: todo))) } catch { @@ -212,8 +226,26 @@ private extension TodoListViewModel { } return effects - case .tapFilterOption(let option): - state.filterOption = option + case .setSortTarget(let target): + state.query.sortTarget = target + state.nextCursor = nil + return [.fetch] + case .setSortOrder(let order): + state.query.sortOrder = order + state.nextCursor = nil + return [.fetch] + case .togglePinnedOnly: + state.query.isPinned = state.query.isPinned == true ? nil : true + state.nextCursor = nil + return [.fetch] + case .setCompletionFilter(let filter): + state.query.completionFilter = filter + state.nextCursor = nil + return [.fetch] + case .resetFilters: + state.query = TodoQuery(kind: state.kind) + state.nextCursor = nil + return [.fetch] case .tapToggleCompleted(let todo): return [.toggleCompleted(todo)] case .tapTogglePinned(let todo): @@ -310,3 +342,38 @@ private extension TodoListViewModel { state.showToast = isPresented } } + +extension TodoQuery.SortTarget { + var title: String { + switch self { + case .createdAt: + return "생성" + case .updatedAt: + return "수정" + } + } +} + +extension TodoQuery.SortOrder { + var title: String { + switch self { + case .latest: + return "최신순" + case .oldest: + return "예전순" + } + } +} + +extension TodoQuery.CompletionFilter { + var title: String { + switch self { + case .all: + return "완료 + 미완료" + case .incomplete: + return "미완료" + case .completed: + return "완료" + } + } +} diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 7e89b91..613f465 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -325,9 +325,6 @@ }, "새 계정 연동" : { - }, - "생성" : { - }, "설명(선택 사항)" : { @@ -349,9 +346,6 @@ }, "알림" : { - }, - "어제" : { - }, "연동된 계정" : { @@ -359,13 +353,13 @@ "완료" : { }, - "읽지 않음" : { + "완료 상태" : { }, - "임시 데이터 삭제" : { + "읽지 않음" : { }, - "작년" : { + "임시 데이터 삭제" : { }, "작성된 내용이 없습니다." : { @@ -377,22 +371,29 @@ "전체 삭제" : { }, - "정렬 옵션" : { + "정렬 기준" : { }, - "정렬: %@" : { + "정렬 순서" : { }, - "제목" : { + "정렬: %@" : { }, - "중요 표시" : { - + "정렬: %@ / %@" : { + "localizations" : { + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "정렬: %1$@ / %2$@" + } + } + } }, - "지난달" : { + "제목" : { }, - "지난주" : { + "중요 표시" : { }, "최근 검색" : { diff --git a/DevLog/UI/Home/TodoListView.swift b/DevLog/UI/Home/TodoListView.swift index 6869037..e30fc6e 100644 --- a/DevLog/UI/Home/TodoListView.swift +++ b/DevLog/UI/Home/TodoListView.swift @@ -11,72 +11,80 @@ struct TodoListView: View { @StateObject var viewModel: TodoListViewModel @EnvironmentObject var router: NavigationRouter @Environment(\.diContainer) var container: DIContainer + @Environment(\.colorScheme) private var colorScheme var body: some View { ZStack { - if viewModel.state.isLoading { - LoadingView() - } else { - if viewModel.state.todos.isEmpty { - VStack { - Spacer() - Text("작성된 내용이 없습니다.") - .foregroundStyle(Color.gray) - Spacer() - } - .frame(maxWidth: .infinity, alignment: .center) - } else { - List(viewModel.state.todos) { todo in - Button { - router.push(Path.detail(todo.id)) - } label: { - TodoItemRow(todo) - } - .listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) - .alignmentGuide(.listRowSeparatorLeading) { _ in return 0 } - .onAppear { - let lastID = viewModel.state.todos.last?.id - if todo.id == lastID, viewModel.state.hasMore { - viewModel.send(.loadNextPage) - } + List { + Section { + if viewModel.state.todos.isEmpty, !viewModel.state.isLoading { + HStack { + Spacer() + Text("작성된 내용이 없습니다.") + .foregroundStyle(Color.gray) + Spacer() } - .swipeActions(edge: .leading) { - Button(action: { - viewModel.send(.tapTogglePinned(todo)) - }) { - Image(systemName: "star\(todo.isPinned ? ".slash" : ".fill")") - } - .tint(Color.orange) + .listRowSeparator(.hidden) + } else { + ForEach(viewModel.state.todos) { todo in Button { - viewModel.send(.tapToggleCompleted(todo)) + router.push(Path.detail(todo.id)) } label: { - Image(systemName: todo.isCompleted ? "arrow.uturn.backward" : "checkmark") + TodoItemRow(todo) } - .tint(Color.green) - } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive, action: { - viewModel.send(.swipeTodo(todo)) - }) { - Image(systemName: "trash") + .listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + .alignmentGuide(.listRowSeparatorLeading) { _ in return 0 } + .onAppear { + let lastID = viewModel.state.todos.last?.id + if todo.id == lastID, viewModel.state.hasMore { + viewModel.send(.loadNextPage) + } + } + .swipeActions(edge: .leading) { + Button(action: { + viewModel.send(.tapTogglePinned(todo)) + }) { + Image(systemName: "star\(todo.isPinned ? ".slash" : ".fill")") + } + .tint(Color.orange) + Button { + viewModel.send(.tapToggleCompleted(todo)) + } label: { + Image(systemName: todo.isCompleted ? "arrow.uturn.backward" : "checkmark") + } + .tint(Color.green) + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button(role: .destructive, action: { + viewModel.send(.swipeTodo(todo)) + }) { + Image(systemName: "trash") + } } } } - .listStyle(.plain) - .refreshable { - viewModel.send(.refresh) - } - .navigationDestination(for: Path.self) { path in - switch path { - case .detail(let todoID): - TodoDetailView(viewModel: TodoDetailViewModel( - fetchUseCase: container.resolve(FetchTodoByIDUseCase.self), - upsertUseCase: container.resolve(UpsertTodoUseCase.self), - todoID: todoID - )) - } - } + } header: { + headerView } + .listRowBackground(Color.clear) + } + .listStyle(.plain) + .refreshable { + viewModel.send(.refresh) + } + .navigationDestination(for: Path.self) { path in + switch path { + case .detail(let todoID): + TodoDetailView(viewModel: TodoDetailViewModel( + fetchUseCase: container.resolve(FetchTodoByIDUseCase.self), + upsertUseCase: container.resolve(UpsertTodoUseCase.self), + todoID: todoID + )) + } + } + + if viewModel.state.isLoading { + LoadingView() } } .alert( @@ -113,73 +121,6 @@ struct TodoListView: View { } .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { - Menu(content: { - Section { - Button(action: { - viewModel.send(.tapFilterOption(.create)) - }) { - if viewModel.state.filterOption == .create { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("생성") - } - Button(action: { - viewModel.send(.tapFilterOption(.update)) - }) { - if viewModel.state.filterOption == .update { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("수정") - } - } header: { - Text("정렬 옵션") - } - - Section { - Button(action: { - viewModel.send(.tapFilterOption(.day)) - }) { - if viewModel.state.filterOption == .day { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("어제") - } - Button(action: { - viewModel.send(.tapFilterOption(.week)) - }) { - if viewModel.state.filterOption == .week { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("지난주") - } - Button(action: { - viewModel.send(.tapFilterOption(.month)) - }) { - if viewModel.state.filterOption == .month { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("지난달") - } - Button(action: { - viewModel.send(.tapFilterOption(.year)) - }) { - if viewModel.state.filterOption == .year { - Image(systemName: "checkmark") - .tint(Color.blue) - } - Text("작년") - } - } header: { - Text("필터 옵션") - } - }, label: { - Image(systemName: "ellipsis") - }) Button { viewModel.send(.setShowEditor(true)) } label: { @@ -207,6 +148,140 @@ struct TodoListView: View { .task { viewModel.send(.onAppear) } } + private var headerView: some View { + ScrollView(.horizontal) { + HStack(spacing: 8) { + if 0 < viewModel.appliedFilterCount { + Menu { + Text("\(viewModel.appliedFilterCount)개 필터가 적용됨") + Button(role: .destructive) { + viewModel.send(.resetFilters) + } label: { + Text("모든 필터 지우기") + } + } label: { + HStack(spacing: 6) { + Image(systemName: "line.3.horizontal.decrease") + filterBadge + } + .adaptiveButtonStyle() + } + } + + sortMenu + filterMenu + } + } + .scrollIndicators(.never) + } + + private var sortMenu: some View { + Menu { + Section { + ForEach([TodoQuery.SortTarget.createdAt, .updatedAt], id: \.self) { option in + Button { + viewModel.send(.setSortTarget(option)) + } label: { + selectionLabel( + title: option.title, + isSelected: viewModel.state.query.sortTarget == option + ) + } + } + } header: { + Text("정렬 기준") + } + + Section { + ForEach([TodoQuery.SortOrder.latest, .oldest], id: \.self) { option in + Button { + viewModel.send(.setSortOrder(option)) + } label: { + selectionLabel( + title: option.title, + isSelected: viewModel.state.query.sortOrder == option + ) + } + } + } header: { + Text("정렬 순서") + } + } label: { + HStack { + Text("정렬: \(viewModel.state.query.sortTarget.title) / \(viewModel.state.query.sortOrder.title)") + Image(systemName: "chevron.down") + } + .adaptiveButtonStyle( + color: viewModel.state.query.sortTarget == .createdAt && + viewModel.state.query.sortOrder == .latest ? .clear : .blue + ) + } + } + + private var filterMenu: some View { + Menu { + Button { + viewModel.send(.togglePinnedOnly) + } label: { + selectionLabel( + title: "중요 표시", + isSelected: viewModel.state.query.isPinned == true + ) + } + + Section { + ForEach([TodoQuery.CompletionFilter.all, .incomplete, .completed], id: \.self) { option in + Button { + viewModel.send(.setCompletionFilter(option)) + } label: { + selectionLabel( + title: option.title, + isSelected: viewModel.state.query.completionFilter == option + ) + } + } + } header: { + Text("완료 상태") + } + } label: { + HStack { + Text("필터 옵션") + Image(systemName: "chevron.down") + } + .adaptiveButtonStyle( + color: viewModel.state.query.isPinned == true || + viewModel.state.query.completionFilter != .all ? .blue : .clear + ) + } + } + + private var filterBadge: some View { + let isDark = colorScheme == .dark + let blue = Color(uiColor: .systemBlue) + let textColor: Color = isDark ? blue : .white + let backgroundColor: Color = isDark ? .white : blue + + return Text("\(viewModel.appliedFilterCount)") + .font(.caption2.weight(.bold)) + .foregroundColor(textColor) + .lineLimit(1) + .minimumScaleFactor(0.6) + .frame(width: 20, height: 20) + .background(Circle().fill(backgroundColor)) + } + + private func selectionLabel(title: String, isSelected: Bool) -> some View { + HStack { + Text(title) + Spacer() + if isSelected { + Image(systemName: "checkmark") + .tint(.blue) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + } + private enum Path: Hashable { case detail(String) } From 049fa5941808e261ee7b79d19e9ff7716b1e4b5a Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 3 Mar 2026 11:42:08 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20Todo=20=EC=97=AC=EB=9F=AC?= =?UTF-8?q?=EA=B0=9C=20=EA=B0=80=EC=A0=B8=EC=98=AC=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=BF=BC=EB=A6=AC=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?fetch=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/App/Assembler/DomainAssembler.swift | 16 ++--------- .../Todo/Fetch/FetchPinnedTodosUseCase.swift | 10 ------- .../Fetch/FetchPinnedTodosUseCaseImpl.swift | 20 -------------- .../Fetch/FetchTodosByKeywordUseCase.swift | 10 ------- .../FetchTodosByKeywordUseCaseImpl.swift | 20 -------------- .../Fetch/FetchTodosUseCase.swift} | 6 ++--- .../Fetch/FetchTodosUseCaseImpl.swift} | 8 +++--- .../Todo/FetchTodosByDateRangeUseCase.swift | 12 --------- .../FetchTodosByDateRangeUseCaseImpl.swift | 27 ------------------- .../ViewModel/HomeViewModel.swift | 9 ++++--- .../ViewModel/ProfileViewModel.swift | 18 ++++++++----- .../ViewModel/SearchViewModel.swift | 10 +++---- .../ViewModel/TodoListViewModel.swift | 10 +++---- DevLog/UI/Common/MainView.swift | 4 +-- DevLog/UI/Home/HomeView.swift | 4 +-- 15 files changed, 40 insertions(+), 144 deletions(-) delete mode 100644 DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCase.swift delete mode 100644 DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCaseImpl.swift delete mode 100644 DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCase.swift delete mode 100644 DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCaseImpl.swift rename DevLog/Domain/UseCase/{UserData/Fetch/Todo/FetchTodosByKindUseCase.swift => Todo/Fetch/FetchTodosUseCase.swift} (50%) rename DevLog/Domain/UseCase/{UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift => Todo/Fetch/FetchTodosUseCaseImpl.swift} (53%) delete mode 100644 DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCase.swift delete mode 100644 DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCaseImpl.swift diff --git a/DevLog/App/Assembler/DomainAssembler.swift b/DevLog/App/Assembler/DomainAssembler.swift index 760b23e..ab6d86b 100644 --- a/DevLog/App/Assembler/DomainAssembler.swift +++ b/DevLog/App/Assembler/DomainAssembler.swift @@ -51,24 +51,12 @@ private extension DomainAssembler { } func registerTodoUseCases(_ container: DIContainer) { - container.register(FetchPinnedTodosUseCase.self) { - FetchPinnedTodosUseCaseImpl(container.resolve(TodoRepository.self)) - } - container.register(FetchTodoByIDUseCase.self) { FetchTodoByIDUseCaseImpl(container.resolve(TodoRepository.self)) } - container.register(FetchTodosByKindUseCase.self) { - FetchTodosByKindUseCaseImpl(container.resolve(TodoRepository.self)) - } - - container.register(FetchTodosByDateRangeUseCase.self) { - FetchTodosByDateRangeUseCaseImpl(container.resolve(TodoRepository.self)) - } - - container.register(FetchTodosByKeywordUseCase.self) { - FetchTodosByKeywordUseCaseImpl(container.resolve(TodoRepository.self)) + container.register(FetchTodosUseCase.self) { + FetchTodosUseCaseImpl(container.resolve(TodoRepository.self)) } container.register(UpsertTodoUseCase.self) { diff --git a/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCase.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCase.swift deleted file mode 100644 index d23a059..0000000 --- a/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCase.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// FetchPinnedTodosUseCase.swift -// DevLog -// -// Created by 최윤진 on 11/29/25. -// - -protocol FetchPinnedTodosUseCase { - func execute() async throws -> [Todo] -} diff --git a/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCaseImpl.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCaseImpl.swift deleted file mode 100644 index 3652370..0000000 --- a/DevLog/Domain/UseCase/Todo/Fetch/FetchPinnedTodosUseCaseImpl.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// FetchPinnedTodosUseCaseImpl.swift -// DevLog -// -// Created by 최윤진 on 11/29/25. -// - -final class FetchPinnedTodosUseCaseImpl: FetchPinnedTodosUseCase { - private let repository: TodoRepository - - init(_ repository: TodoRepository) { - self.repository = repository - } - - func execute() async throws -> [Todo] { - let query = TodoQuery(isPinned: true) - let page = try await repository.fetchTodos(query, cursor: nil) - return page.items - } -} diff --git a/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCase.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCase.swift deleted file mode 100644 index 83711a3..0000000 --- a/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCase.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// FetchTodosByKeywordUseCase.swift -// DevLog -// -// Created by opfic on 2/21/26. -// - -protocol FetchTodosByKeywordUseCase { - func execute(_ keyword: String) async throws -> [Todo] -} diff --git a/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCaseImpl.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCaseImpl.swift deleted file mode 100644 index 207165d..0000000 --- a/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosByKeywordUseCaseImpl.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// FetchTodosByKeywordUseCaseImpl.swift -// DevLog -// -// Created by opfic on 2/21/26. -// - -final class FetchTodosByKeywordUseCaseImpl: FetchTodosByKeywordUseCase { - private let repository: TodoRepository - - init(_ repository: TodoRepository) { - self.repository = repository - } - - func execute(_ keyword: String) async throws -> [Todo] { - let query = TodoQuery(keyword: keyword) - let page = try await repository.fetchTodos(query, cursor: nil) - return page.items - } -} diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCase.swift similarity index 50% rename from DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift rename to DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCase.swift index f161362..8216a61 100644 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCase.swift +++ b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCase.swift @@ -1,10 +1,10 @@ // -// FetchTodosByKindUseCase.swift +// FetchTodosUseCase.swift // DevLog // -// Created by 최윤진 on 2/1/26. +// Created by opfic on 3/3/26. // -protocol FetchTodosByKindUseCase { +protocol FetchTodosUseCase { func execute(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage } diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCaseImpl.swift similarity index 53% rename from DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift rename to DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCaseImpl.swift index bfe754b..b20382c 100644 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByKindUseCaseImpl.swift +++ b/DevLog/Domain/UseCase/Todo/Fetch/FetchTodosUseCaseImpl.swift @@ -1,11 +1,11 @@ // -// FetchTodosByKindUseCaseImpl.swift +// FetchTodosUseCaseImpl.swift // DevLog // -// Created by 최윤진 on 2/1/26. +// Created by opfic on 3/3/26. // -final class FetchTodosByKindUseCaseImpl: FetchTodosByKindUseCase { +final class FetchTodosUseCaseImpl: FetchTodosUseCase { private let repository: TodoRepository init(_ repository: TodoRepository) { @@ -13,6 +13,6 @@ final class FetchTodosByKindUseCaseImpl: FetchTodosByKindUseCase { } func execute(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage { - return try await repository.fetchTodos(query, cursor: cursor) + try await repository.fetchTodos(query, cursor: cursor) } } diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCase.swift b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCase.swift deleted file mode 100644 index 86f739f..0000000 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCase.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// FetchTodosByDateRangeUseCase.swift -// DevLog -// -// Created by opfic on 3/1/26. -// - -import Foundation - -protocol FetchTodosByDateRangeUseCase { - func execute(from startDate: Date, to endDate: Date) async throws -> [Todo] -} diff --git a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCaseImpl.swift b/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCaseImpl.swift deleted file mode 100644 index 76418d9..0000000 --- a/DevLog/Domain/UseCase/UserData/Fetch/Todo/FetchTodosByDateRangeUseCaseImpl.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// FetchTodosByDateRangeUseCaseImpl.swift -// DevLog -// -// Created by opfic on 3/1/26. -// - -import Foundation - -final class FetchTodosByDateRangeUseCaseImpl: FetchTodosByDateRangeUseCase { - private let repository: TodoRepository - - init(_ repository: TodoRepository) { - self.repository = repository - } - - func execute(from startDate: Date, to endDate: Date) async throws -> [Todo] { - let query = TodoQuery( - createdAtFrom: startDate, - createdAtTo: endDate, - pageSize: 100, - fetchAllPages: true - ) - let page = try await repository.fetchTodos(query, cursor: nil) - return page.items - } -} diff --git a/DevLog/Presentation/ViewModel/HomeViewModel.swift b/DevLog/Presentation/ViewModel/HomeViewModel.swift index 6497868..a9bff5b 100644 --- a/DevLog/Presentation/ViewModel/HomeViewModel.swift +++ b/DevLog/Presentation/ViewModel/HomeViewModel.swift @@ -85,7 +85,7 @@ final class HomeViewModel: Store { private let upsertTodoUseCase: UpsertTodoUseCase private let addWebPageUseCase: AddWebPageUseCase private let deleteWebPageUseCase: DeleteWebPageUseCase - private let fetchPinnedTodosUseCase: FetchPinnedTodosUseCase + private let fetchTodosUseCase: FetchTodosUseCase private let fetchWebPagesUseCase: FetchWebPagesUseCase private var pendingTask: (WebPageItem, Int)? @@ -93,13 +93,13 @@ final class HomeViewModel: Store { addWebPageUseCase: AddWebPageUseCase, deleteWebPageUseCase: DeleteWebPageUseCase, upsertTodoUseCase: UpsertTodoUseCase, - fetchPinnedTodosUseCase: FetchPinnedTodosUseCase, + fetchTodosUseCase: FetchTodosUseCase, fetchWebPagesUseCase: FetchWebPagesUseCase ) { self.addWebPageUseCase = addWebPageUseCase self.deleteWebPageUseCase = deleteWebPageUseCase self.upsertTodoUseCase = upsertTodoUseCase - self.fetchPinnedTodosUseCase = fetchPinnedTodosUseCase + self.fetchTodosUseCase = fetchTodosUseCase self.fetchWebPagesUseCase = fetchWebPagesUseCase } @@ -168,7 +168,8 @@ final class HomeViewModel: Store { do { defer { send(.setPinnedLoading(false)) } send(.setPinnedLoading(true)) - let todos = try await fetchPinnedTodosUseCase.execute() + let page = try await fetchTodosUseCase.execute(TodoQuery(isPinned: true), cursor: nil) + let todos = page.items send(.fetchPinnedTodos(todos.map { PinnedTodoItem(from: $0) })) } catch { send(.setAlert(isPresented: true, type: .error)) diff --git a/DevLog/Presentation/ViewModel/ProfileViewModel.swift b/DevLog/Presentation/ViewModel/ProfileViewModel.swift index 9a76a20..bb3ac8e 100644 --- a/DevLog/Presentation/ViewModel/ProfileViewModel.swift +++ b/DevLog/Presentation/ViewModel/ProfileViewModel.swift @@ -53,7 +53,7 @@ final class ProfileViewModel: Store { @Published private(set) var state = State() private let fetchUserDataUseCase: FetchUserDataUseCase - private let fetchTodosByDateRangeUseCase: FetchTodosByDateRangeUseCase + private let fetchTodosUseCase: FetchTodosUseCase private let upsertStatusMessageUseCase: UpsertStatusMessageUseCase private let fetchHeatmapActivityTypesUseCase: FetchProfileHeatmapActivityTypesUseCase private let updateHeatmapActivityTypesUseCase: UpdateProfileHeatmapActivityTypesUseCase @@ -96,13 +96,13 @@ final class ProfileViewModel: Store { init( fetchUserDataUseCase: FetchUserDataUseCase, - fetchTodosByDateRangeUseCase: FetchTodosByDateRangeUseCase, + fetchTodosUseCase: FetchTodosUseCase, upsertStatusMessageUseCase: UpsertStatusMessageUseCase, fetchHeatmapActivityTypesUseCase: FetchProfileHeatmapActivityTypesUseCase, updateHeatmapActivityTypesUseCase: UpdateProfileHeatmapActivityTypesUseCase ) { self.fetchUserDataUseCase = fetchUserDataUseCase - self.fetchTodosByDateRangeUseCase = fetchTodosByDateRangeUseCase + self.fetchTodosUseCase = fetchTodosUseCase self.upsertStatusMessageUseCase = upsertStatusMessageUseCase self.fetchHeatmapActivityTypesUseCase = fetchHeatmapActivityTypesUseCase self.updateHeatmapActivityTypesUseCase = updateHeatmapActivityTypesUseCase @@ -279,10 +279,16 @@ private extension ProfileViewModel { return [] } - return try await fetchTodosByDateRangeUseCase.execute( - from: quarterStart, - to: nextQuarterStart + let page = try await fetchTodosUseCase.execute( + TodoQuery( + createdAtFrom: quarterStart, + createdAtTo: nextQuarterStart, + pageSize: 100, + fetchAllPages: true + ), + cursor: nil ) + return page.items } func canMove(to quarterStart: Date, calendar: Calendar, today: Date) -> Bool { diff --git a/DevLog/Presentation/ViewModel/SearchViewModel.swift b/DevLog/Presentation/ViewModel/SearchViewModel.swift index d810073..c3b7268 100644 --- a/DevLog/Presentation/ViewModel/SearchViewModel.swift +++ b/DevLog/Presentation/ViewModel/SearchViewModel.swift @@ -44,7 +44,7 @@ final class SearchViewModel: Store { @Published private(set) var state: State = .init() private let fetchWebPagesUseCase: FetchWebPagesUseCase - private let fetchTodosByKeywordUseCase: FetchTodosByKeywordUseCase + private let fetchTodosUseCase: FetchTodosUseCase private let fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase private let updateRecentSearchQueriesUseCase: UpdateRecentSearchQueriesUseCase let contentsLimit: Int = 5 @@ -55,12 +55,12 @@ final class SearchViewModel: Store { init( fetchWebPagesUseCase: FetchWebPagesUseCase, - fetchTodosByKeywordUseCase: FetchTodosByKeywordUseCase, + fetchTodosUseCase: FetchTodosUseCase, fetchRecentSearchQueriesUseCase: FetchRecentSearchQueriesUseCase, updateRecentSearchQueriesUseCase: UpdateRecentSearchQueriesUseCase ) { self.fetchWebPagesUseCase = fetchWebPagesUseCase - self.fetchTodosByKeywordUseCase = fetchTodosByKeywordUseCase + self.fetchTodosUseCase = fetchTodosUseCase self.fetchRecentSearchQueriesUseCase = fetchRecentSearchQueriesUseCase self.updateRecentSearchQueriesUseCase = updateRecentSearchQueriesUseCase self.state.recentQueries = OrderedSet(fetchRecentSearchQueriesUseCase.execute()) @@ -134,9 +134,9 @@ final class SearchViewModel: Store { do { send(.setLoading(true)) defer { send(.setLoading(false)) } - async let todos = fetchTodosByKeywordUseCase.execute(query) + async let todos = fetchTodosUseCase.execute(TodoQuery(keyword: query), cursor: nil) async let webPages = fetchWebPagesUseCase.execute(query) - let todoItems = try await todos.map { TodoListItem(from: $0) } + let todoItems = try await todos.items.map { TodoListItem(from: $0) } let webPageItems = try await webPages.map { WebPageItem(from: $0) } send(.fetchTodos(todoItems)) send(.fetchWebPage(webPageItems)) diff --git a/DevLog/Presentation/ViewModel/TodoListViewModel.swift b/DevLog/Presentation/ViewModel/TodoListViewModel.swift index 023c0f5..0ca43bf 100644 --- a/DevLog/Presentation/ViewModel/TodoListViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoListViewModel.swift @@ -69,19 +69,19 @@ final class TodoListViewModel: Store { } @Published private(set) var state: State - private let fetchTodosByKindUseCase: FetchTodosByKindUseCase + private let fetchTodosUseCase: FetchTodosUseCase private let fetchTodoByIDUseCase: FetchTodoByIDUseCase private let upsertTodoUseCase: UpsertTodoUseCase private let deleteTodoUseCase: DeleteTodoUseCase init( - fetchTodosByKindUseCase: FetchTodosByKindUseCase, + fetchTodosUseCase: FetchTodosUseCase, fetchTodoByIDUseCase: FetchTodoByIDUseCase, upsertTodoUseCase: UpsertTodoUseCase, deleteTodoUseCase: DeleteTodoUseCase, kind: TodoKind ) { - self.fetchTodosByKindUseCase = fetchTodosByKindUseCase + self.fetchTodosUseCase = fetchTodosUseCase self.fetchTodoByIDUseCase = fetchTodoByIDUseCase self.upsertTodoUseCase = upsertTodoUseCase self.deleteTodoUseCase = deleteTodoUseCase @@ -128,7 +128,7 @@ final class TodoListViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let page = try await fetchTodosByKindUseCase.execute(state.query, cursor: nil) + let page = try await fetchTodosUseCase.execute(state.query, cursor: nil) send(.resetPagination) send(.appendTodos(page.items.map { TodoListItem(from: $0) }, nextCursor: page.nextCursor)) let hasMore = page.items.count == state.query.pageSize && page.nextCursor != nil @@ -142,7 +142,7 @@ final class TodoListViewModel: Store { do { defer { send(.setLoading(false)) } send(.setLoading(true)) - let page = try await fetchTodosByKindUseCase.execute(state.query, cursor: state.nextCursor) + let page = try await fetchTodosUseCase.execute(state.query, cursor: state.nextCursor) send(.appendTodos(page.items.map { TodoListItem(from: $0) }, nextCursor: page.nextCursor)) let hasMore = page.items.count == state.query.pageSize && page.nextCursor != nil send(.setHasMore(hasMore)) diff --git a/DevLog/UI/Common/MainView.swift b/DevLog/UI/Common/MainView.swift index 049cd3d..69e4cef 100644 --- a/DevLog/UI/Common/MainView.swift +++ b/DevLog/UI/Common/MainView.swift @@ -16,7 +16,7 @@ struct MainView: View { addWebPageUseCase: container.resolve(AddWebPageUseCase.self), deleteWebPageUseCase: container.resolve(DeleteWebPageUseCase.self), upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), - fetchPinnedTodosUseCase: container.resolve(FetchPinnedTodosUseCase.self), + fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self) )) .tabItem { @@ -36,7 +36,7 @@ struct MainView: View { } ProfileView(viewModel: ProfileViewModel( fetchUserDataUseCase: container.resolve(FetchUserDataUseCase.self), - fetchTodosByDateRangeUseCase: container.resolve(FetchTodosByDateRangeUseCase.self), + fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), upsertStatusMessageUseCase: container.resolve(UpsertStatusMessageUseCase.self), fetchHeatmapActivityTypesUseCase: container.resolve(FetchProfileHeatmapActivityTypesUseCase.self), updateHeatmapActivityTypesUseCase: container.resolve(UpdateProfileHeatmapActivityTypesUseCase.self) diff --git a/DevLog/UI/Home/HomeView.swift b/DevLog/UI/Home/HomeView.swift index 397fc15..fd91eee 100644 --- a/DevLog/UI/Home/HomeView.swift +++ b/DevLog/UI/Home/HomeView.swift @@ -26,7 +26,7 @@ struct HomeView: View { switch path { case .kind(let todoKind): TodoListView(viewModel: TodoListViewModel( - fetchTodosByKindUseCase: container.resolve(FetchTodosByKindUseCase.self), + fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), fetchTodoByIDUseCase: container.resolve(FetchTodoByIDUseCase.self), upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self), deleteTodoUseCase: container.resolve(DeleteTodoUseCase.self), @@ -90,7 +90,7 @@ struct HomeView: View { )) { SearchView(viewModel: SearchViewModel( fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self), - fetchTodosByKeywordUseCase: container.resolve(FetchTodosByKeywordUseCase.self), + fetchTodosUseCase: container.resolve(FetchTodosUseCase.self), fetchRecentSearchQueriesUseCase: container.resolve(FetchRecentSearchQueriesUseCase.self), updateRecentSearchQueriesUseCase: container.resolve(UpdateRecentSearchQueriesUseCase.self) ))