@@ -14,137 +14,223 @@ final class TodoViewModel: Store {
1414 let kind : TodoKind
1515 var showEditor : Bool = false
1616 var showAlert : Bool = false
17+ var alertTitle : String = " "
1718 var alertMessage : String = " "
1819 var scope : TodoScope = . title
1920 var filterOption : FilterOption = . create
20- var isLoading = false
21+ var isLoading : Bool = false
22+ var showToast : Bool = false
23+ var toastMessage : String = " "
24+ var pendingTask : ( Todo , Int ) ?
2125 }
2226
2327 enum FilterOption {
2428 case create, update, day, week, month, year
2529 }
2630
2731 enum Action {
28- // Modifier
29- case onAppear, refresh
30-
3132 // User
32- case tapTogglePinned( Todo )
33+ case refresh
34+ case setAlert( Bool )
35+ case setShowEditor( Bool )
3336 case swipeTodo( Todo )
3437 case tapFilterOption( FilterOption )
35- case upsertTodo( Todo )
38+ case tapTogglePinned( Todo )
39+ case undoDelete
3640
37- // Binding
38- case openEditor
39- case closeEditor
40- case closeAlert
41+ // View
42+ case confirmDelete
43+ case onAppear
4144 case setScope( TodoScope )
4245 case setSearchText( String )
46+ case setToast( isPresented: Bool )
47+ case upsertTodo( Todo )
4348
44- // Call from run
45- case didFetchTodos( [ Todo ] )
46- case didLoading( Bool )
47- case didShowAlert( String )
49+ // Run
4850 case didTogglePinned( Todo )
51+ case setLoading( Bool )
52+ case setTodos( [ Todo ] )
4953 }
5054
5155 enum SideEffect {
52- case fetchTodos
53- case upsertTodo( Todo )
56+ case fetch
57+ case upsert( Todo )
58+ case delete( Todo )
5459 case togglePinned( Todo )
55- case swipeTodo( Todo )
5660 }
5761
5862 private let fetchTodosByKindUseCase : FetchTodosByKindUseCase
5963 private let upsertTodoUseCase : UpsertTodoUseCase
64+ private let deleteTodoUseCase : DeleteTodoUseCase
6065 @Published private( set) var state : State
6166
6267 init (
6368 fetchTodosByKindUseCase: FetchTodosByKindUseCase ,
6469 upsertTodoUseCase: UpsertTodoUseCase ,
70+ deleteTodoUseCase: DeleteTodoUseCase ,
6571 kind: TodoKind
6672 ) {
6773 self . fetchTodosByKindUseCase = fetchTodosByKindUseCase
6874 self . upsertTodoUseCase = upsertTodoUseCase
75+ self . deleteTodoUseCase = deleteTodoUseCase
6976 self . state = State ( kind: kind)
7077 }
7178
7279 func reduce( with action: Action ) -> [ SideEffect ] {
80+ var state = self . state
81+ var effects : [ SideEffect ] = [ ]
82+
7383 switch action {
74- case . onAppear, . refresh:
75- return [ . fetchTodos]
76- case . tapTogglePinned( let todo) :
77- return [ . togglePinned( todo) ]
78- case . swipeTodo( let todo) :
79- return [ . swipeTodo( todo) ]
80- case . tapFilterOption( let option) :
81- state. filterOption = option
82- case . upsertTodo( let todo) :
83- return [ . upsertTodo( todo) ]
84- case . openEditor:
85- state. showEditor = true
86- case . closeEditor:
87- state. showEditor = false
88- case . closeAlert:
89- state. showAlert = false
90- case . setScope( let scope) :
91- state. scope = scope
92- case . setSearchText( let text) :
93- state. searchText = text
94- case . didFetchTodos( let todos) :
95- 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
101- case . didTogglePinned( let todo) :
102- if let index = state. todos. firstIndex ( where: { $0. id == todo. id } ) {
103- state. todos [ index] = todo
104- }
84+ case . refresh, . setAlert, . setShowEditor, . swipeTodo, . tapFilterOption, . tapTogglePinned, . undoDelete:
85+ effects = reduceByUser ( action, state: & state)
86+
87+ case . confirmDelete, . onAppear, . setScope, . setSearchText, . setToast, . upsertTodo:
88+ effects = reduceByView ( action, state: & state)
89+
90+ case . didTogglePinned, . setLoading, . setTodos:
91+ effects = reduceByRun ( action, state: & state)
10592 }
106- return [ ]
93+
94+ self . state = state
95+ return effects
10796 }
10897
10998 func run( _ effect: SideEffect ) {
11099 switch effect {
111- case . fetchTodos :
100+ case . fetch :
112101 Task {
113102 do {
114- defer { send ( . didLoading ( false ) ) }
115- send ( . didLoading ( true ) )
103+ defer { send ( . setLoading ( false ) ) }
104+ send ( . setLoading ( true ) )
116105 let todos = try await fetchTodosByKindUseCase. execute ( state. kind)
117- send ( . didFetchTodos ( todos) )
106+ send ( . setTodos ( todos) )
118107 } catch {
119- send ( . didShowAlert ( error . localizedDescription ) )
108+ send ( . setAlert ( true ) )
120109 }
121110 }
122- case . upsertTodo ( let todo ) :
111+ case . upsert ( let item ) :
123112 Task {
124113 do {
125- defer { send ( . didLoading ( false ) ) }
126- send ( . didLoading ( true ) )
127- try await upsertTodoUseCase. execute ( todo )
114+ defer { send ( . setLoading ( false ) ) }
115+ send ( . setLoading ( true ) )
116+ try await upsertTodoUseCase. execute ( item )
128117 send ( . refresh)
129118 } catch {
130- send ( . didShowAlert ( error . localizedDescription ) )
119+ send ( . setAlert ( true ) )
131120 }
132121 }
133- case . togglePinned( let todo ) :
122+ case . togglePinned( let item ) :
134123 Task {
135124 do {
136- defer { send ( . didLoading ( false ) ) }
137- send ( . didLoading ( true ) )
138- var todo = todo
125+ defer { send ( . setLoading ( false ) ) }
126+ send ( . setLoading ( true ) )
127+ var todo = item
139128 todo. isPinned. toggle ( )
140129 try await upsertTodoUseCase. execute ( todo)
141130 send ( . didTogglePinned( todo) )
142131 } catch {
143- send ( . didShowAlert ( error . localizedDescription ) )
132+ send ( . setAlert ( true ) )
144133 }
145134 }
135+ case . delete( let item) :
136+ Task {
137+ do {
138+ try await deleteTodoUseCase. execute ( item. id)
139+ } catch {
140+ send ( . setAlert( true ) )
141+ }
142+ }
143+ }
144+ }
145+ }
146+
147+ // MARK: - Reduce Methods
148+ private extension TodoViewModel {
149+ func reduceByUser( _ action: Action , state: inout State ) -> [ SideEffect ] {
150+ switch action {
151+ case . refresh:
152+ return [ . fetch]
153+ case . setAlert( let value) :
154+ setAlert ( & state, isPresented: value)
155+ case . setShowEditor( let value) :
156+ state. showEditor = value
146157 case . swipeTodo( let todo) :
158+ guard let index = state. todos. firstIndex ( where: { $0. id == todo. id } ) else {
159+ return [ ]
160+ }
161+ state. pendingTask = ( todo, index)
162+ state. todos. remove ( at: index)
163+ setToast ( & state, isPresented: true )
164+ case . tapFilterOption( let option) :
165+ state. filterOption = option
166+ case . tapTogglePinned( let todo) :
167+ return [ . togglePinned( todo) ]
168+ case . undoDelete:
169+ guard let ( todo, index) = state. pendingTask else { return [ ] }
170+ state. todos. insert ( todo, at: index)
171+ state. pendingTask = nil
172+ default :
173+ break
174+ }
175+ return [ ]
176+ }
177+
178+ func reduceByView( _ action: Action , state: inout State ) -> [ SideEffect ] {
179+ switch action {
180+ case . confirmDelete:
181+ guard let ( item, _) = state. pendingTask else {
182+ return [ ]
183+ }
184+ return [ . delete( item) ]
185+ case . onAppear:
186+ return [ . fetch]
187+ case . setScope( let scope) :
188+ state. scope = scope
189+ case . setSearchText( let text) :
190+ state. searchText = text
191+ case . setToast( let isPresented) :
192+ setToast ( & state, isPresented: isPresented)
193+ case . upsertTodo( let todo) :
194+ return [ . upsert( todo) ]
195+ default :
196+ break
197+ }
198+ return [ ]
199+ }
200+
201+ func reduceByRun( _ action: Action , state: inout State ) -> [ SideEffect ] {
202+ switch action {
203+ case . didTogglePinned( let todo) :
204+ if let index = state. todos. firstIndex ( where: { $0. id == todo. id } ) {
205+ state. todos [ index] = todo
206+ }
207+ case . setLoading( let value) :
208+ state. isLoading = value
209+ case . setTodos( let todos) :
210+ state. todos = todos
211+ default :
147212 break
148213 }
214+ return [ ]
215+ }
216+ }
217+
218+ // MARK: - Helper Methods
219+ private extension TodoViewModel {
220+ func setAlert(
221+ _ state: inout State ,
222+ isPresented: Bool
223+ ) {
224+ state. alertTitle = " 오류 "
225+ state. alertMessage = " 문제가 발생했습니다. 잠시 후 다시 시도해주세요. "
226+ state. showAlert = isPresented
227+ }
228+
229+ func setToast(
230+ _ state: inout State ,
231+ isPresented: Bool
232+ ) {
233+ state. toastMessage = " 실행 취소 "
234+ state. showToast = isPresented
149235 }
150236}
0 commit comments