Skip to content

Commit 40ba57c

Browse files
authored
[Fix/#40] Todo의 종류별 분류한 뷰에 들어가면 Todo가 보이지 않는 문제를 해결한다 (#41)
1 parent a1594b5 commit 40ba57c

File tree

7 files changed

+140
-69
lines changed

7 files changed

+140
-69
lines changed

DevLog/App/Assembler/DomainAssembler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,9 @@ final class DomainAssembler: Assembler {
4646
container.register(UpdatePushSettingsUseCase.self) {
4747
UpdatePushSettingsUseCaseImpl(container.resolve(PushNotificationRepository.self))
4848
}
49+
50+
container.register(FetchTodosByKindUseCase.self) {
51+
FetchTodosByKindUseCaseImpl(container.resolve(TodoRepository.self))
52+
}
4953
}
5054
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//
2+
// FetchTodosByKindUseCase.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/1/26.
6+
//
7+
8+
protocol FetchTodosByKindUseCase {
9+
var repository: TodoRepository { get }
10+
func execute(_ kind: TodoKind) async throws -> [Todo]
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// FetchTodosByKindUseCaseImpl.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/1/26.
6+
//
7+
8+
final class FetchTodosByKindUseCaseImpl: FetchTodosByKindUseCase {
9+
let repository: TodoRepository
10+
11+
init(_ repository: TodoRepository) {
12+
self.repository = repository
13+
}
14+
15+
func execute(_ kind: TodoKind) async throws -> [Todo] {
16+
return try await repository.fetchTodos(kind)
17+
}
18+
}

DevLog/Presentation/ViewModel/TodoViewModel.swift

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ final class TodoViewModel: Store {
1111
struct State {
1212
var todos: [Todo] = []
1313
var searchText: String = ""
14-
var kind: TodoKind
14+
let kind: TodoKind
1515
var showEditor: Bool = false
16-
var showToast: Bool = false
17-
var toastMessage: String = ""
16+
var showAlert: Bool = false
17+
var alertMessage: String = ""
1818
var scope: TodoScope = .title
1919
var filterOption: FilterOption = .create
2020
var isLoading = false
@@ -37,12 +37,14 @@ final class TodoViewModel: Store {
3737
// Binding
3838
case openEditor
3939
case closeEditor
40-
case closeToast
40+
case closeAlert
4141
case setScope(TodoScope)
4242
case setSearchText(String)
4343

4444
// Call from run
4545
case didFetchTodos([Todo])
46+
case didLoading(Bool)
47+
case didShowAlert(String)
4648
case didTogglePinned(Todo)
4749
}
4850

@@ -53,13 +55,16 @@ final class TodoViewModel: Store {
5355
case swipeTodo(Todo)
5456
}
5557

58+
private let fetchTodosByKindUseCase: FetchTodosByKindUseCase
5659
private let upsertTodoUseCase: UpsertTodoUseCase
5760
@Published private(set) var state: State
5861

5962
init(
63+
fetchTodosByKindUseCase: FetchTodosByKindUseCase,
6064
upsertTodoUseCase: UpsertTodoUseCase,
6165
kind: TodoKind
6266
) {
67+
self.fetchTodosByKindUseCase = fetchTodosByKindUseCase
6368
self.upsertTodoUseCase = upsertTodoUseCase
6469
self.state = State(kind: kind)
6570
}
@@ -80,14 +85,19 @@ final class TodoViewModel: Store {
8085
state.showEditor = true
8186
case .closeEditor:
8287
state.showEditor = false
83-
case .closeToast:
84-
state.showToast = false
88+
case .closeAlert:
89+
state.showAlert = false
8590
case .setScope(let scope):
8691
state.scope = scope
8792
case .setSearchText(let text):
8893
state.searchText = text
8994
case .didFetchTodos(let todos):
9095
state.todos = todos
96+
case .didLoading(let value):
97+
state.isLoading = value
98+
case .didShowAlert(let message):
99+
state.alertMessage = message
100+
state.showAlert = true
91101
case .didTogglePinned(let todo):
92102
if let index = state.todos.firstIndex(where: { $0.id == todo.id }) {
93103
state.todos[index] = todo
@@ -99,11 +109,26 @@ final class TodoViewModel: Store {
99109
func run(_ effect: SideEffect) {
100110
switch effect {
101111
case .fetchTodos:
102-
break
112+
Task {
113+
do {
114+
defer { send(.didLoading(false)) }
115+
send(.didLoading(true))
116+
let todos = try await fetchTodosByKindUseCase.execute(state.kind)
117+
send(.didFetchTodos(todos))
118+
} catch {
119+
send(.didShowAlert(error.localizedDescription))
120+
}
121+
}
103122
case .upsertTodo(let todo):
104123
Task {
105-
try await upsertTodoUseCase.execute(todo)
106-
send(.refresh)
124+
do {
125+
defer { send(.didLoading(false)) }
126+
send(.didLoading(true))
127+
try await upsertTodoUseCase.execute(todo)
128+
send(.refresh)
129+
} catch {
130+
send(.didShowAlert(error.localizedDescription))
131+
}
107132
}
108133
case .togglePinned(let todo):
109134
break

DevLog/Resource/Localizable.xcstrings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@
277277
},
278278
"베타 테스트 참여" : {
279279

280+
},
281+
"불러오기 실패" : {
282+
280283
},
281284
"사용자 설정" : {
282285

DevLog/UI/Home/HomeView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ struct HomeView: View {
121121
.navigationDestination(for: Path.self) { path in
122122
switch path {
123123
case .kind(let todoKind):
124-
TodoView(viewModel: TodoViewModel(
124+
TodoView(viewModel:TodoViewModel(
125+
fetchTodosByKindUseCase: container.resolve(FetchTodosByKindUseCase.self),
125126
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
126127
kind: todoKind
127128
))

DevLog/UI/Home/TodoView.swift

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,70 +12,86 @@ struct TodoView: View {
1212
@EnvironmentObject var router: NavigationRouter
1313

1414
var body: some View {
15-
VStack {
16-
if viewModel.state.todos.isEmpty {
17-
VStack {
18-
Spacer()
19-
Text("작성된 내용이 없습니다.")
20-
.foregroundStyle(Color.gray)
21-
Spacer()
22-
}
23-
.frame(maxWidth: .infinity, alignment: .center)
15+
ZStack {
16+
if viewModel.state.isLoading {
17+
LoadingView()
2418
} else {
25-
List(viewModel.state.todos) { todo in
26-
Button {
27-
router.push(Path.detail(todo))
28-
} label: {
29-
VStack(alignment: .leading, spacing: 5) {
30-
HStack {
31-
if todo.isPinned {
32-
Image(systemName: "star.fill")
19+
if viewModel.state.todos.isEmpty {
20+
VStack {
21+
Spacer()
22+
Text("작성된 내용이 없습니다.")
23+
.foregroundStyle(Color.gray)
24+
Spacer()
25+
}
26+
.frame(maxWidth: .infinity, alignment: .center)
27+
} else {
28+
List(viewModel.state.todos) { todo in
29+
Button {
30+
router.push(Path.detail(todo))
31+
} label: {
32+
VStack(alignment: .leading, spacing: 5) {
33+
HStack {
34+
if todo.isPinned {
35+
Image(systemName: "star.fill")
36+
.font(.headline)
37+
.foregroundStyle(Color.orange)
38+
}
39+
Text(todo.title)
3340
.font(.headline)
34-
.foregroundStyle(Color.orange)
41+
.lineLimit(1)
3542
}
36-
Text(todo.title)
37-
.font(.headline)
43+
Text(todo.content)
44+
.font(.subheadline)
45+
.foregroundStyle(Color.gray)
3846
.lineLimit(1)
3947
}
40-
Text(todo.content)
41-
.font(.subheadline)
42-
.foregroundStyle(Color.gray)
43-
.lineLimit(1)
48+
.padding(.vertical, 5)
4449
}
45-
.padding(.vertical, 5)
46-
}
47-
.swipeActions(edge: .leading) {
48-
Button(action: {
49-
viewModel.send(.tapTogglePinned(todo))
50-
}) {
51-
Image(systemName: "star\(todo.isPinned ? ".slash" : ".fill")")
52-
}
53-
.tint(Color.orange)
54-
}
55-
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
56-
Button(role: .destructive, action: {
57-
viewModel.send(.swipeTodo(todo))
58-
}) {
59-
Image(systemName: "trash")
50+
.swipeActions(edge: .leading) {
51+
Button(action: {
52+
viewModel.send(.tapTogglePinned(todo))
53+
}) {
54+
Image(systemName: "star\(todo.isPinned ? ".slash" : ".fill")")
55+
}
56+
.tint(Color.orange)
6057
}
58+
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
59+
Button(role: .destructive, action: {
60+
viewModel.send(.swipeTodo(todo))
61+
}) {
62+
Image(systemName: "trash")
63+
}
6164

65+
}
6266
}
63-
}
64-
.listStyle(.plain)
65-
.refreshable {
66-
viewModel.send(.refresh)
67-
}
68-
.navigationDestination(for: Path.self) { path in
69-
switch path {
70-
case .detail(let todo):
71-
TodoDetailView(
72-
todo: todo,
73-
onSubmit: { viewModel.send(.upsertTodo($0)) }
74-
)
67+
.listStyle(.plain)
68+
.refreshable {
69+
viewModel.send(.refresh)
70+
}
71+
.navigationDestination(for: Path.self) { path in
72+
switch path {
73+
case .detail(let todo):
74+
TodoDetailView(
75+
todo: todo,
76+
onSubmit: { viewModel.send(.upsertTodo($0)) }
77+
)
78+
}
7579
}
7680
}
7781
}
7882
}
83+
.alert("불러오기 실패", isPresented: Binding(
84+
get: { viewModel.state.showAlert },
85+
set: { _, _ in }
86+
)) {
87+
Button(role: .cancel, action: {
88+
viewModel.send(.closeAlert)
89+
}) {
90+
Text("확인")
91+
}
92+
} message: {
93+
Text(viewModel.state.alertMessage)
94+
}
7995
.navigationTitle(viewModel.state.kind.localizedName)
8096
.navigationBarTitleDisplayMode(.large)
8197
.fullScreenCover(isPresented: Binding(
@@ -181,14 +197,7 @@ struct TodoView: View {
181197
Text(scope.localizedName).tag(scope)
182198
}
183199
}
184-
.task {
185-
viewModel.send(.onAppear)
186-
}
187-
.overlay {
188-
if viewModel.state.isLoading {
189-
LoadingView()
190-
}
191-
}
200+
.task { viewModel.send(.onAppear) }
192201
}
193202

194203
private enum Path: Hashable {

0 commit comments

Comments
 (0)