Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 105 additions & 53 deletions DevLog/Presentation/ViewModel/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,35 @@ import Foundation

final class HomeViewModel: Store {
struct State {
// 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 = ""
var isSearching: Bool = false
var reorderTodo: Bool = false

// Side Effect UI
var isLoading: Bool = false
var toastMessage: String = ""
var showToast: Bool = false
var showAlert: Bool = false
var alertTitle: String = ""
var alertMessage: String = ""
}

enum Action {
// Life Cycle
case onAppear

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

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

// Call from run
case didFetchPinnedTodos([Todo])
case setAlert(Bool)
case onAppear
case updateSearching(Bool)
case updateSearchText(String)
case upsertTodo(Todo)
case fetchPinnedTodos([Todo])
case setLoading(Bool)
}

enum SideEffect {
Expand All @@ -69,53 +59,115 @@ final class HomeViewModel: Store {

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

switch action {
case .onAppear:
return [.fetchPinnedTodos]
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
case .tapTodoKind, .orderTodoKindPreferences, .setReorderTodo,
.setShowTodoEditor, .setShowTodoKindPicker, .setShowSearchView, .setAlert:
effects = reduceByUser(action, state: &state)

case .onAppear, .updateSearching, .updateSearchText, .upsertTodo:
effects = reduceByView(action, state: &state)

case .fetchPinnedTodos, .setLoading:
effects = reduceByRun(action, state: &state)
}

self.state = state
return []
return effects
}

func run(_ effect: SideEffect) {
switch effect {
case .upsertTodo(let todo):
Task {
try await upsertTodoUseCase.execute(todo)
do {
defer { send(.setLoading(false)) }
send(.setLoading(true))
try await upsertTodoUseCase.execute(todo)
} catch {
send(.setAlert(true))
}
}
case .fetchPinnedTodos:
Task {
let todos = try await fetchPinnedTodosUseCase.execute()
send(.didFetchPinnedTodos(todos))
do {
defer { send(.setLoading(false)) }
send(.setLoading(true))
let todos = try await fetchPinnedTodosUseCase.execute()
send(.fetchPinnedTodos(todos))
} catch {
send(.setAlert(true))
}
}
}
}
}

// MARK: - Reduce Methods
private extension HomeViewModel {
func reduceByUser(_ action: Action, state: inout State) -> [SideEffect] {
switch action {
case .tapTodoKind(let kind):
state.selectedTodoKind = kind
state.showTodoKindPicker = false
state.showTodoEditor = true
case .orderTodoKindPreferences(let preferences):
state.todoKindPreferences = preferences
case .setReorderTodo(let isPresented):
state.reorderTodo = isPresented
case .setShowTodoEditor(let isPresented):
state.showTodoEditor = isPresented
if !isPresented { state.selectedTodoKind = nil }
case .setShowTodoKindPicker(let isPresented):
state.showTodoKindPicker = isPresented
case .setShowSearchView(let isPresented):
state.showSearchView = isPresented
case .setAlert(let isPresented):
setAlert(&state, isPresented: isPresented)
default:
break
}
return []
}

func reduceByView(_ action: Action, state: inout State) -> [SideEffect] {
switch action {
case .onAppear:
return [.fetchPinnedTodos]
case .updateSearching(let isSearching):
state.isSearching = isSearching
case .updateSearchText(let text):
state.searchText = text
case .upsertTodo(let todo):
return [.upsertTodo(todo)]
default:
break
}
return []
}

func reduceByRun(_ action: Action, state: inout State) -> [SideEffect] {
switch action {
case .fetchPinnedTodos(let todos):
state.pinnedTodos = todos
case .setLoading(let isLoading):
state.isLoading = isLoading
default:
break
}
return []
}
}

// MARK: - Helper Methods
private extension HomeViewModel {
func setAlert(
_ state: inout State,
isPresented: Bool
) {
state.alertTitle = "오류"
state.alertMessage = "문제가 발생했습니다. 잠시 후 다시 시도해주세요."
state.showAlert = isPresented
}
}
36 changes: 17 additions & 19 deletions DevLog/UI/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,20 @@ struct HomeView: View {
fetchWebPagesUseCase: container.resolve(FetchWebPagesUseCase.self)
))
}
.alert("", isPresented: Binding(
get: { viewModel.state.showToast },
set: { _, _ in })
.alert(
viewModel.state.alertTitle,
isPresented: Binding(
get: { viewModel.state.showAlert },
set: { viewModel.send(.setAlert($0)) }
)
) {
Button(action: {
viewModel.send(.setShowToast(false))
}) {
Text("확인")
}
Button("확인", role: .cancel) { }
} message: {
Text(viewModel.state.toastMessage)
Text(viewModel.state.alertMessage)
}
.onAppear {
viewModel.send(.onAppear)
}
.overlay {
if viewModel.state.isLoading {
LoadingView()
}
}
}
}

Expand Down Expand Up @@ -144,11 +138,15 @@ struct HomeView: View {
private var pinnedSection: some View {
Section(content: {
if viewModel.state.pinnedTodos.isEmpty {
HStack {
Spacer()
Text("최근에 중요 표시를 한 Todo가 여기 표시됩니다.")
.font(.callout)
Spacer()
if viewModel.state.isLoading {
LoadingView()
} else {
HStack {
Spacer()
Text("최근에 중요 표시를 한 Todo가 여기 표시됩니다.")
.font(.callout)
Spacer()
}
}
} else {
ForEach(viewModel.state.pinnedTodos, id: \.id) { todo in
Expand Down