Skip to content

Commit c9c9065

Browse files
authored
[#598] refreshable의 로딩 인디케이터와 LoadingView가 같이 뜨는 현상을 해결한다 (#625)
* refactor: refreshable fetch 흐름 분리 * fix: refreshable 로딩 인디케이터 분리 * fix: refresh 알림 관찰 재구독
1 parent b29ec97 commit c9c9065

7 files changed

Lines changed: 86 additions & 36 deletions

File tree

Application/DevLogPresentation/Sources/Home/List/TodoListFeature.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,11 @@ private extension TodoListFeature {
201201
func fetchEffect(
202202
query: TodoQuery,
203203
cursor: TodoCursor?,
204-
resetsPagination: Bool = true
204+
resetsPagination: Bool = true,
205+
showsIndicator: Bool = true
205206
) -> Effect<Action> {
206207
.concatenate(
207-
.send(.loading(.begin(target: .default, mode: .delayed))),
208+
showsIndicator ? .send(.loading(.begin(target: .default, mode: .delayed))) : .none,
208209
.run { [fetchTodosUseCase] send in
209210
do {
210211
let page = try await fetchTodosUseCase.execute(query, cursor: cursor)
@@ -216,12 +217,16 @@ private extension TodoListFeature {
216217
nextCursor: page.nextCursor
217218
)))
218219
await send(.store(.setHasMore(page.items.count == query.pageSize && page.nextCursor != nil)))
219-
await send(.loading(.end(target: .default, mode: .delayed)))
220+
if showsIndicator {
221+
await send(.loading(.end(target: .default, mode: .delayed)))
222+
}
220223
} catch is CancellationError {
221224
return
222225
} catch {
223226
await send(.store(.setAlert(true)))
224-
await send(.loading(.end(target: .default, mode: .delayed)))
227+
if showsIndicator {
228+
await send(.loading(.end(target: .default, mode: .delayed)))
229+
}
225230
}
226231
}
227232
)
@@ -277,7 +282,9 @@ private extension TodoListFeature {
277282
state: inout State
278283
) -> Effect<Action> {
279284
switch action {
280-
case .refresh, .onAppear:
285+
case .refresh:
286+
return fetchEffect(query: state.query, cursor: nil, showsIndicator: false)
287+
case .onAppear:
281288
return fetchEffect(query: state.query, cursor: nil)
282289
case .swipeTodo(let todo):
283290
return swipeTodoEffect(todo, state: &state)

Application/DevLogPresentation/Sources/Home/List/TodoListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ struct TodoListView: View {
171171
}
172172
.offset(y: headerOffset)
173173
}
174-
.refreshable { store.send(.view(.refresh)) }
174+
.refreshable { await store.send(.view(.refresh)).finish() }
175175
.scrollDisabled(visibleTodos.isEmpty || store.state.isLoading)
176176

177177
if store.state.isLoading {

Application/DevLogPresentation/Sources/Profile/ProfileFeature.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,11 @@ struct ProfileFeature {
108108
if !settings.isEmpty {
109109
state.selectedActivityKinds = settings
110110
}
111+
let showsIndicator = if case .fetchData = action { true } else { false }
111112
if let selectedQuarterStart = state.selectedQuarterStart {
112113
return .merge(
113114
fetchUserDataEffect(),
114-
fetchActivityQuarterEffect(selectedQuarterStart)
115+
fetchActivityQuarterEffect(selectedQuarterStart, showsIndicator: showsIndicator)
115116
)
116117
}
117118
return fetchUserDataEffect()
@@ -220,9 +221,14 @@ private extension ProfileFeature {
220221
}
221222
}
222223

223-
func fetchActivityQuarterEffect(_ quarterStart: Date) -> Effect<Action> {
224+
func fetchActivityQuarterEffect(
225+
_ quarterStart: Date,
226+
showsIndicator: Bool = true
227+
) -> Effect<Action> {
224228
.run { [fetchTodosUseCase] send in
225-
await send(.loading(.begin(target: .default, mode: .delayed)))
229+
if showsIndicator {
230+
await send(.loading(.begin(target: .default, mode: .delayed)))
231+
}
226232
do {
227233
let data = try await ProfileHeatmapBuilder.fetchQuarterActivityData(
228234
from: quarterStart,
@@ -237,9 +243,13 @@ private extension ProfileFeature {
237243
)
238244
)
239245
)
240-
await send(.loading(.end(target: .default, mode: .delayed)))
246+
if showsIndicator {
247+
await send(.loading(.end(target: .default, mode: .delayed)))
248+
}
241249
} catch {
242-
await send(.loading(.end(target: .default, mode: .delayed)))
250+
if showsIndicator {
251+
await send(.loading(.end(target: .default, mode: .delayed)))
252+
}
243253
await send(.setAlert(true))
244254
}
245255
}

Application/DevLogPresentation/Sources/PushNotification/PushNotificationListFeature.swift

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct PushNotificationListFeature {
5858
case loading(LoadingFeature.Action)
5959

6060
enum ViewAction: Equatable {
61+
case refresh
6162
case fetchNotifications
6263
case loadNextPage
6364
case deleteNotification(PushNotificationItem)
@@ -83,7 +84,6 @@ struct PushNotificationListFeature {
8384
case syncNotifications([PushNotificationItem], nextCursor: PushNotificationCursor?, hasMore: Bool)
8485
case setNotificationHidden(String, Bool)
8586
case setNotificationRead(String, Bool)
86-
case observeNotifications(PushNotificationQuery, Int)
8787
}
8888
}
8989

@@ -151,6 +151,14 @@ private extension PushNotificationListFeature {
151151
state: inout State
152152
) -> Effect<Action> {
153153
switch action {
154+
case .refresh:
155+
state.nextCursor = nil
156+
return fetchNotificationsEffect(
157+
query: state.query,
158+
cursor: nil,
159+
existingCount: 0,
160+
showsIndicator: false
161+
)
154162
case .fetchNotifications:
155163
state.nextCursor = nil
156164
return fetchNotificationsEffect(query: state.query, cursor: nil, existingCount: 0)
@@ -252,8 +260,6 @@ private extension PushNotificationListFeature {
252260
if let index = state.notifications.firstIndex(where: { $0.id == notificationId }) {
253261
state.notifications[index].isRead = isRead
254262
}
255-
case .observeNotifications(let query, let limit):
256-
return observeNotificationsEffect(query: query, limit: limit)
257263
}
258264

259265
return .none
@@ -269,11 +275,36 @@ private extension PushNotificationListFeature {
269275
func fetchNotificationsEffect(
270276
query: PushNotificationQuery,
271277
cursor: PushNotificationCursor?,
272-
existingCount: Int
278+
existingCount: Int,
279+
showsIndicator: Bool = true
273280
) -> Effect<Action> {
274281
let limit = max(query.pageSize, existingCount)
275-
let fetchEffect: Effect<Action> = .run { [fetchPushNotificationsUseCase] send in
276-
await send(.loading(.begin(target: .default, mode: .delayed)))
282+
let fetchEffect = fetchNotificationsPageEffect(query: query, cursor: cursor, showsIndicator: showsIndicator)
283+
let observeEffect = observeNotificationsEffect(
284+
query: query,
285+
limit: max(limit, existingCount + query.pageSize)
286+
)
287+
288+
if cursor == nil {
289+
return .concatenate(
290+
.cancel(id: CancelID.observeNotifications),
291+
fetchEffect,
292+
observeEffect
293+
)
294+
}
295+
296+
return fetchEffect
297+
}
298+
299+
func fetchNotificationsPageEffect(
300+
query: PushNotificationQuery,
301+
cursor: PushNotificationCursor?,
302+
showsIndicator: Bool = true
303+
) -> Effect<Action> {
304+
.run { [fetchPushNotificationsUseCase] send in
305+
if showsIndicator {
306+
await send(.loading(.begin(target: .default, mode: .delayed)))
307+
}
277308
do {
278309
let page = try await fetchPushNotificationsUseCase.execute(query, cursor: cursor)
279310
if cursor == nil {
@@ -286,23 +317,17 @@ private extension PushNotificationListFeature {
286317
))
287318
)
288319
await send(.store(.setHasMore(page.items.count == query.pageSize && page.nextCursor != nil)))
289-
await send(.store(.observeNotifications(query, max(limit, existingCount + page.items.count))))
290-
await send(.loading(.end(target: .default, mode: .delayed)))
320+
if showsIndicator {
321+
await send(.loading(.end(target: .default, mode: .delayed)))
322+
}
291323
} catch {
292-
await send(.loading(.end(target: .default, mode: .delayed)))
324+
if showsIndicator {
325+
await send(.loading(.end(target: .default, mode: .delayed)))
326+
}
293327
await send(.store(.setAlert))
294328
}
295329
}
296330
.cancellable(id: CancelID.fetchNotifications, cancelInFlight: true)
297-
298-
if cursor == nil {
299-
return .concatenate(
300-
.cancel(id: CancelID.observeNotifications),
301-
fetchEffect
302-
)
303-
}
304-
305-
return fetchEffect
306331
}
307332

308333
func observeNotificationsEffect(

Application/DevLogPresentation/Sources/PushNotification/PushNotificationListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct PushNotificationListView: View {
3838
headerOffset = max(0, -offset)
3939
}
4040
.safeAreaInset(edge: .top) { safeAreaHeader }
41-
.refreshable { store.send(.view(.fetchNotifications)) }
41+
.refreshable { await store.send(.view(.refresh)).finish() }
4242
.navigationTitle(String(localized: "nav_push_notifications"))
4343
.listStyle(.plain)
4444
}

Application/DevLogPresentation/Sources/Today/TodayFeature.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ struct TodayFeature {
185185
return updateDisplayOptionsEffect(state.displayOptions)
186186
case .binding:
187187
break
188-
case .refresh, .fetchData:
188+
case .refresh:
189+
return fetchTodosEffect(showsIndicator: false)
190+
case .fetchData:
189191
return fetchTodosEffect()
190192
case .setSectionScope(let scope):
191193
if state.selectedSectionScope == scope, scope != .all {
@@ -255,9 +257,11 @@ private enum UpdateTodayDisplayOptionsUseCaseKey: DependencyKey {
255257
}
256258

257259
private extension TodayFeature {
258-
func fetchTodosEffect() -> Effect<Action> {
260+
func fetchTodosEffect(showsIndicator: Bool = true) -> Effect<Action> {
259261
.run { [fetchTodosUseCase] send in
260-
await send(.loading(.begin(target: .default, mode: .delayed)))
262+
if showsIndicator {
263+
await send(.loading(.begin(target: .default, mode: .delayed)))
264+
}
261265
do {
262266
async let todosWithDueDatePage = fetchTodosUseCase.execute(
263267
TodoQuery(
@@ -284,9 +288,13 @@ private extension TodayFeature {
284288
let todosWithDueDate = try await todosWithDueDatePage.items.compactMap(TodayTodoItem.init(from:))
285289
let todosWithoutDueDate = try await todosWithoutDueDatePage.items.compactMap(TodayTodoItem.init(from:))
286290
await send(.store(.setTodos(todosWithDueDate + todosWithoutDueDate)))
287-
await send(.loading(.end(target: .default, mode: .delayed)))
291+
if showsIndicator {
292+
await send(.loading(.end(target: .default, mode: .delayed)))
293+
}
288294
} catch {
289-
await send(.loading(.end(target: .default, mode: .delayed)))
295+
if showsIndicator {
296+
await send(.loading(.end(target: .default, mode: .delayed)))
297+
}
290298
await send(.store(.setAlert))
291299
}
292300
}

Application/DevLogPresentation/Sources/Today/TodayView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct TodayView: View {
3939
.navigationTitle(String(localized: "nav_today"))
4040
.toolbar { toolbarContent }
4141
.background(NavigationBarConfigurator())
42-
.refreshable { store.send(.refresh) }
42+
.refreshable { await store.send(.refresh).finish() }
4343
.alert($store.scope(state: \.alert, action: \.alert))
4444
.overlay {
4545
if store.isLoading {

0 commit comments

Comments
 (0)