Skip to content
58 changes: 38 additions & 20 deletions DevLog/Presentation/ViewModel/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ final class HomeViewModel: Store {
// UI
var todoKindPreferences = TodoKind.allCases.map { TodoKindPreference(kind: $0, isVisible: true) }
var pinnedTodos: [Todo] = []
var showTodoKindPicker: Bool = false
var showTodoEditor: Bool = false
var showSearchView: Bool = false
var selectedTodoKind: TodoKind?

// User Input
var searchText: String = ""
Expand All @@ -29,15 +33,18 @@ final class HomeViewModel: Store {
case onAppear

// User
case tapEllipsisButton
case tapTodoKind(TodoKind)
case upsertTodo(Todo)
case orderTodoKindPreferences([TodoKindPreference])

// Binding
case updateSearching(Bool)
case updateSearchText(String)
case closeOrderingSheet
case closeToast
case setReorderTodo(Bool)
case setShowTodoEditor(Bool)
case setShowTodoKindPicker(Bool)
case setShowSearchView(Bool)
case setShowToast(Bool)

// Call from run
case didFetchPinnedTodos([Todo])
Expand All @@ -61,29 +68,40 @@ final class HomeViewModel: Store {
}

func reduce(with action: Action) -> [SideEffect] {
var state = self.state
switch action {
case .onAppear:
return [.fetchPinnedTodos]
case.tapEllipsisButton:
state.reorderTodo = true
case .updateSearching(let isSearching):
state.isSearching = isSearching
case .updateSearchText(let text):
state.searchText = text
case .upsertTodo(let todo):
return [.upsertTodo(todo)]
case .orderTodoKindPreferences(let preferences):
state.todoKindPreferences = preferences

case .closeOrderingSheet:
state.reorderTodo = false
case .closeToast:
state.showToast = false

case .tapTodoKind(let kind):
state.selectedTodoKind = kind
state.showTodoKindPicker = false
state.showTodoEditor = true
case .updateSearching(let value):
state.isSearching = value
case .updateSearchText(let value):
state.searchText = value
case .setReorderTodo(let value):
state.reorderTodo = value
case .setShowTodoEditor(let value):
state.showTodoEditor = value
if !value {
state.selectedTodoKind = nil
}
case .setShowTodoKindPicker(let value):
state.showTodoKindPicker = value
case .setShowSearchView(let value):
state.showSearchView = value
case .setShowToast(let value):
state.showToast = value
case .upsertTodo(let value):
return [.upsertTodo(value)]
case .orderTodoKindPreferences(let value):
state.todoKindPreferences = value
case .didFetchPinnedTodos(let todos):
state.pinnedTodos = todos
}


self.state = state
return []
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// NotificationViewModel.swift
// PushNotificationViewModel.swift
// DevLog
//
// Created by 최윤진 on 11/22/25.
Expand Down
122 changes: 24 additions & 98 deletions DevLog/Presentation/ViewModel/SearchViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,93 +10,61 @@ import OrderedCollections

final class SearchViewModel: Store {
struct State {
var alertMsg: String = ""
var isLoading: Bool = false
var isSearching: Bool = false
var newURL: String = "https://"
var searchQuery: String = ""
var selectedWebPage: WebPageItem?
var showAlert: Bool = false
var alertTitle: String = ""
var alertType: AlertType?
var alertMessage: String = ""
var webPages: OrderedSet<WebPageItem> = []
var filteredWebPages: [WebPageItem] {
if searchQuery.isEmpty {
// TODO: 자주 방문한 것을 보여주는 기능으로 변경 필요
return []
} else {
return webPages.filter {
$0.title.localizedCaseInsensitiveContains(searchQuery) ||
$0.displayURL.localizedCaseInsensitiveContains(searchQuery)
}
webPages.filter {
$0.title.localizedCaseInsensitiveContains(searchQuery) ||
$0.displayURL.localizedCaseInsensitiveContains(searchQuery)
}
}
}

enum Action {
case addWebPage(WebPageItem? = nil)
case deleteWebPage(item: WebPageItem, fromEffect: Bool = false)
case onAppear
case fetchWebPage([WebPageItem]? = nil)
case selectWebPage(WebPageItem)
case setAlert(isPresented: Bool, type: AlertType? = nil)
case setLoading(Bool)
case setNewURL(String = "https://")
case setSearching(Bool)
case setSearchQuery(String)
}

enum SideEffect {
case fetch
case add(String)
case delete(WebPageItem)
}

enum AlertType {
case addWebPage, error
}

@Published private(set) var state: State = .init()
private let fetchWebPagesUseCase: FetchWebPagesUseCase
private let addWebPageUseCase: AddWebPageUseCase
private let deleteWebPageUseCase: DeleteWebPageUseCase

init(
fetchWebPagesUseCase: FetchWebPagesUseCase,
addWebPageUseCase: AddWebPageUseCase,
deleteWebPageUseCase: DeleteWebPageUseCase
) {
init(fetchWebPagesUseCase: FetchWebPagesUseCase) {
self.fetchWebPagesUseCase = fetchWebPagesUseCase
self.addWebPageUseCase = addWebPageUseCase
self.deleteWebPageUseCase = deleteWebPageUseCase
}

func reduce(with action: Action) -> [SideEffect] {
var state = self.state

switch action {
case .addWebPage(let item):
guard let item else { return [.add(state.newURL)] }
state.webPages.append(item)
case .deleteWebPage(let info, let fromEffect):
if !fromEffect { return [.delete(info)] }
state.webPages.removeAll { $0.url == info.url }
case .onAppear:
state.isSearching = true
self.state = state
return [.fetch]
case .fetchWebPage(let items):
guard let items else { return [.fetch] }
guard let items else {
self.state = state
return [.fetch]
}
state.webPages = OrderedSet(items)
case .selectWebPage(let newValue):
state.selectedWebPage = newValue
case .setAlert(let isPresented, let type):
setAlert(isPresented: isPresented, for: type)
return []
case .setLoading(let newValue):
state.isLoading = newValue
case .setNewURL(let newValue):
state.newURL = newValue
case .setSearching(let newValue):
state.isSearching = newValue
case .setSearchQuery(let newValue):
state.searchQuery = newValue
case .selectWebPage(let item):
state.selectedWebPage = item
case .setLoading(let isLoading):
state.isLoading = isLoading
case .setSearching(let isSearching):
state.isSearching = isSearching
case .setSearchQuery(let query):
state.searchQuery = query
}

self.state = state
Expand All @@ -108,56 +76,14 @@ final class SearchViewModel: Store {
case .fetch:
Task {
do {
defer { send(.setLoading(false)) }
send(.setLoading(true))
let items = try await fetchWebPagesUseCase.execute().map { WebPageItem(from: $0) }
send(.fetchWebPage(items))
} catch {
send(.setAlert(isPresented: true, type: .error))
}
}
case .add(let urlString):
Task {
do {
defer { send(.setLoading(false)) }
send(.setLoading(true))
let metadata = try await addWebPageUseCase.execute(urlString)
let item = WebPageItem(from: metadata)
send(.addWebPage(item))
send(.setNewURL())
} catch {
send(.setAlert(isPresented: true, type: .error))
}
}
case .delete(let item):
Task {
do {
defer { send(.setLoading(false)) }
send(.setLoading(true))
try await deleteWebPageUseCase.execute(item.url.absoluteString)
send(.deleteWebPage(item: item, fromEffect: true))
let items = try await self.fetchWebPagesUseCase.execute().map { WebPageItem(from: $0) }
send(.fetchWebPage(items))
} catch {
send(.setAlert(isPresented: true, type: .error))

}
}
}
}
}

private extension SearchViewModel {
func setAlert(isPresented: Bool, for type: AlertType?) {
switch type {
case .addWebPage:
state.alertTitle = "웹 페이지 추가"
state.alertMessage = ""
case .error:
state.alertTitle = "오류"
state.alertMessage = "문제가 발생했습니다. 잠시 다시 시도해주세요."
case .none:
state.alertTitle = ""
state.alertMessage = ""
}
state.alertType = type
state.showAlert = isPresented
}
}
20 changes: 10 additions & 10 deletions DevLog/Resource/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
},
"%@ 검색" : {

},
"DevLog 검색" : {

},
"lime_green" : {
"extractionState" : "manual",
Expand Down Expand Up @@ -221,6 +218,9 @@
},
"TODO" : {

},
"TODO 종류" : {

},
"TODO 편집" : {

Expand All @@ -245,14 +245,20 @@
}
}
},
"URL" : {
"Web Pages" : {

},
"개인정보 처리방침" : {

},
"검색" : {

},
"검색 결과가 없습니다." : {

},
"검색어를 입력해 저장한 앱 컨텐츠를 찾아보세요." : {

},
"계정 삭제" : {

Expand Down Expand Up @@ -316,9 +322,6 @@
},
"알림" : {

},
"앱 내 컨텐츠를 검색할 수 있어요." : {

},
"어제" : {

Expand All @@ -337,9 +340,6 @@
},
"작성된 알림이 없습니다." : {

},
"저장된 웹페이지가 없습니다.\n우측 '+' 버튼을 눌러 웹페이지를 추가해보세요." : {

},
"정렬 옵션" : {

Expand Down
9 changes: 0 additions & 9 deletions DevLog/UI/Common/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ struct MainView: View {
Image(systemName: "bell.fill")
Text("알림")
}
SearchView(viewModel: SearchViewModel(
fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self),
addWebPageUseCase: container.resolve(AddWebPageUseCase.self),
deleteWebPageUseCase: container.resolve(DeleteWebPageUseCase.self)
))
.tabItem {
Image(systemName: "magnifyingglass")
Text("검색")
}
ProfileView(viewModel: ProfileViewModel(
fetchUserDataUseCase: container.resolve(FetchUserDataUseCase.self),
upsertStatusMessageUseCase: container.resolve(UpsertStatusMessageUseCase.self)
Expand Down
Loading