@@ -18,41 +18,49 @@ final class TodoViewModel: Store {
1818 var scope : TodoScope = . title
1919 var filterOption : FilterOption = . create
2020 var isLoading = false
21+ var showToast : Bool = false
22+ var toastMessage : String = " "
23+ var pendingTask : ( Todo , Int ) ?
2124 }
2225
2326 enum FilterOption {
2427 case create, update, day, week, month, year
2528 }
2629
2730 enum Action {
28- // Modifier
29- case onAppear, refresh
30-
3131 // User
3232 case tapTogglePinned( Todo )
3333 case swipeTodo( Todo )
3434 case tapFilterOption( FilterOption )
3535 case upsertTodo( Todo )
36+ case undoDelete
37+ case confirmDelete
3638
37- // Binding
39+ // View
40+ case onAppear, refresh
3841 case openEditor
3942 case closeEditor
4043 case closeAlert
4144 case setScope( TodoScope )
4245 case setSearchText( String )
46+ case setToast( isPresented: Bool , type: ToastType ? = nil )
47+ case setLoading( Bool )
48+ case setTodos( [ Todo ] )
4349
44- // Call from run
45- case didFetchTodos( [ Todo ] )
46- case didLoading( Bool )
50+ // Run
4751 case didShowAlert( String )
4852 case didTogglePinned( Todo )
4953 }
54+
55+ enum ToastType {
56+ case delete
57+ }
5058
5159 enum SideEffect {
52- case fetchTodos
53- case upsertTodo( Todo )
60+ case fetch
61+ case upsert( Todo )
62+ case delete( Todo )
5463 case togglePinned( Todo )
55- case swipeTodo( Todo )
5664 }
5765
5866 private let fetchTodosByKindUseCase : FetchTodosByKindUseCase
@@ -73,17 +81,33 @@ final class TodoViewModel: Store {
7381 }
7482
7583 func reduce( with action: Action ) -> [ SideEffect ] {
84+ var state = self . state
85+
7686 switch action {
7787 case . onAppear, . refresh:
78- return [ . fetchTodos ]
88+ return [ . fetch ]
7989 case . tapTogglePinned( let todo) :
8090 return [ . togglePinned( todo) ]
8191 case . swipeTodo( let todo) :
82- return [ . swipeTodo( todo) ]
92+ guard let index = state. todos. firstIndex ( where: { $0. id == todo. id } ) else {
93+ return [ ]
94+ }
95+ state. pendingTask = ( todo, index)
96+ state. todos. remove ( at: index)
97+ setToast ( & state, isPresented: true , for: . delete)
8398 case . tapFilterOption( let option) :
8499 state. filterOption = option
85100 case . upsertTodo( let todo) :
86- return [ . upsertTodo( todo) ]
101+ return [ . upsert( todo) ]
102+ case . undoDelete:
103+ guard let ( todo, index) = state. pendingTask else { return [ ] }
104+ state. todos. insert ( todo, at: index)
105+ state. pendingTask = nil
106+ case . confirmDelete:
107+ guard let ( item, _) = state. pendingTask else {
108+ return [ ]
109+ }
110+ return [ . delete( item) ]
87111 case . openEditor:
88112 state. showEditor = true
89113 case . closeEditor:
@@ -94,10 +118,12 @@ final class TodoViewModel: Store {
94118 state. scope = scope
95119 case . setSearchText( let text) :
96120 state. searchText = text
97- case . didFetchTodos ( let todos ) :
98- state. todos = todos
99- case . didLoading ( let value) :
121+ case . setToast ( let isPresented , let type ) :
122+ setToast ( & state, isPresented : isPresented , for : type )
123+ case . setLoading ( let value) :
100124 state. isLoading = value
125+ case . setTodos( let todos) :
126+ state. todos = todos
101127 case . didShowAlert( let message) :
102128 state. alertMessage = message
103129 state. showAlert = true
@@ -106,57 +132,71 @@ final class TodoViewModel: Store {
106132 state. todos [ index] = todo
107133 }
108134 }
135+
136+ self . state = state
109137 return [ ]
110138 }
111139
112140 func run( _ effect: SideEffect ) {
113141 switch effect {
114- case . fetchTodos :
142+ case . fetch :
115143 Task {
116144 do {
117- defer { send ( . didLoading ( false ) ) }
118- send ( . didLoading ( true ) )
145+ defer { send ( . setLoading ( false ) ) }
146+ send ( . setLoading ( true ) )
119147 let todos = try await fetchTodosByKindUseCase. execute ( state. kind)
120- send ( . didFetchTodos ( todos) )
148+ send ( . setTodos ( todos) )
121149 } catch {
122150 send ( . didShowAlert( error. localizedDescription) )
123151 }
124152 }
125- case . upsertTodo ( let todo ) :
153+ case . upsert ( let item ) :
126154 Task {
127155 do {
128- defer { send ( . didLoading ( false ) ) }
129- send ( . didLoading ( true ) )
130- try await upsertTodoUseCase. execute ( todo )
156+ defer { send ( . setLoading ( false ) ) }
157+ send ( . setLoading ( true ) )
158+ try await upsertTodoUseCase. execute ( item )
131159 send ( . refresh)
132160 } catch {
133161 send ( . didShowAlert( error. localizedDescription) )
134162 }
135163 }
136- case . togglePinned( let todo ) :
164+ case . togglePinned( let item ) :
137165 Task {
138166 do {
139- defer { send ( . didLoading ( false ) ) }
140- send ( . didLoading ( true ) )
141- var todo = todo
167+ defer { send ( . setLoading ( false ) ) }
168+ send ( . setLoading ( true ) )
169+ var todo = item
142170 todo. isPinned. toggle ( )
143171 try await upsertTodoUseCase. execute ( todo)
144172 send ( . didTogglePinned( todo) )
145173 } catch {
146174 send ( . didShowAlert( error. localizedDescription) )
147175 }
148176 }
149- case . swipeTodo ( let todo ) :
177+ case . delete ( let item ) :
150178 Task {
151179 do {
152- defer { send ( . didLoading( false ) ) }
153- send ( . didLoading( true ) )
154- try await deleteTodoUseCase. execute ( todo. id)
155- send ( . refresh)
180+ try await deleteTodoUseCase. execute ( item. id)
156181 } catch {
157182 send ( . didShowAlert( error. localizedDescription) )
158183 }
159184 }
160185 }
161186 }
162187}
188+ private extension TodoViewModel {
189+ func setToast(
190+ _ state: inout State ,
191+ isPresented: Bool ,
192+ for type: ToastType ?
193+ ) {
194+ switch type {
195+ case . delete:
196+ state. toastMessage = " 실행 취소 "
197+ case . none:
198+ state. toastMessage = " "
199+ }
200+ state. showToast = isPresented
201+ }
202+ }
0 commit comments