Skip to content

Commit bbc7db3

Browse files
committed
Add manual/auto refresh for topics
1 parent a341d76 commit bbc7db3

File tree

4 files changed

+45
-7
lines changed

4 files changed

+45
-7
lines changed

Modules/Sources/AnalyticsClient/Events/TopicEvent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
public enum TopicEvent: Event {
11-
// case onRefresh
11+
case onRefresh
1212
case userAvatarTapped(Int)
1313
case urlTapped(URL)
1414

Modules/Sources/TopicFeature/Analytics/TopicFeature+Analytics.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ extension TopicFeature {
1919
var body: some Reducer<State, Action> {
2020
Reduce<State, Action> { state, action in
2121
switch action {
22-
case .onTask, .pageNavigation, ._loadTypes:
22+
case .onTask, .onSceneBecomeActive, .pageNavigation, ._loadTypes:
2323
break
2424

25+
case .onRefresh:
26+
analytics.log(TopicEvent.onRefresh)
27+
2528
case let .userAvatarTapped(userId: userId):
2629
analytics.log(TopicEvent.userAvatarTapped(userId))
2730

Modules/Sources/TopicFeature/TopicFeature.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public struct TopicFeature: Reducer, Sendable {
3939

4040
var isFirstPage = true
4141
var isLoadingTopic = true
42+
var isRefreshing = false
4243

4344
var pageNavigation = PageNavigationFeature.State(type: .topic)
4445

@@ -70,6 +71,8 @@ public struct TopicFeature: Reducer, Sendable {
7071

7172
public enum Action {
7273
case onTask
74+
case onRefresh
75+
case onSceneBecomeActive
7376
case userAvatarTapped(userId: Int)
7477
case urlTapped(URL)
7578
case pageNavigation(PageNavigationFeature.Action)
@@ -112,6 +115,19 @@ public struct TopicFeature: Reducer, Sendable {
112115
.send(._loadTopic(offset: state.initialOffset))
113116
)
114117

118+
case .onRefresh:
119+
state.isRefreshing = true
120+
return .run { [offset = state.pageNavigation.offset] send in
121+
await send(._loadTopic(offset: offset))
122+
}
123+
124+
case .onSceneBecomeActive:
125+
if state.isLoadingTopic || state.isRefreshing {
126+
return .none
127+
} else {
128+
return .send(.onRefresh)
129+
}
130+
115131
case .userAvatarTapped:
116132
// TODO: Wrap into Delegate action?
117133
return .none
@@ -121,6 +137,7 @@ public struct TopicFeature: Reducer, Sendable {
121137
return .none
122138

123139
case let .pageNavigation(.offsetChanged(to: newOffset)):
140+
state.isRefreshing = false
124141
return .concatenate([
125142
.run { [isLastPage = state.pageNavigation.isLastPage, topicId = state.topicId] _ in
126143
if isLastPage {
@@ -165,10 +182,16 @@ public struct TopicFeature: Reducer, Sendable {
165182

166183
case let ._loadTopic(offset):
167184
state.isFirstPage = offset == 0
168-
state.isLoadingTopic = true
169-
return .run { [id = state.topicId, perPage = state.appSettings.topicPerPage] send in
170-
let result = await Result { try await apiClient.getTopic(id, offset, perPage) }
171-
await send(._topicResponse(result))
185+
if !state.isRefreshing {
186+
state.isLoadingTopic = true
187+
}
188+
return .run { [id = state.topicId, perPage = state.appSettings.topicPerPage, isRefreshing = state.isRefreshing] send in
189+
let startTime = Date()
190+
let topic = try await apiClient.getTopic(id, offset, perPage)
191+
if isRefreshing { await delayUntilTimePassed(1.0, since: startTime) }
192+
await send(._topicResponse(.success(topic)))
193+
} catch: { error, send in
194+
await send(._topicResponse(.failure(error)))
172195
}
173196
.cancellable(id: CancelID.loading)
174197

@@ -207,6 +230,7 @@ public struct TopicFeature: Reducer, Sendable {
207230
case let ._loadTypes(types):
208231
state.types = types
209232
state.isLoadingTopic = false
233+
state.isRefreshing = false
210234
reportFullyDisplayed(&state)
211235
return .none
212236
// return PageNavigationFeature()
@@ -215,6 +239,7 @@ public struct TopicFeature: Reducer, Sendable {
215239

216240
case let ._topicResponse(.failure(error)):
217241
print("TOPIC RESPONSE FAILURE: \(error)")
242+
state.isRefreshing = false
218243
reportFullyDisplayed(&state)
219244
return .none
220245

Modules/Sources/TopicFeature/TopicScreen.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public struct TopicScreen: View {
1919

2020
@Perception.Bindable public var store: StoreOf<TopicFeature>
2121

22+
@Environment(\.scenePhase) private var scenePhase
2223
@Environment(\.tintColor) private var tintColor
2324
@State private var scrollProxy: ScrollViewProxy?
2425
@State private var scrollScale: CGFloat = 1
@@ -56,18 +57,27 @@ public struct TopicScreen: View {
5657
.scrollDismissesKeyboard(.immediately)
5758
}
5859
}
60+
.refreshable {
61+
// Wrapper around finish() due to SwiftUI bug
62+
await Task { await store.send(.onRefresh).finish() }.value
63+
}
5964
.overlay {
6065
if store.topic == nil || store.isLoadingTopic {
6166
PDALoader()
6267
.frame(width: 24, height: 24)
6368
}
6469
}
6570
.navigationTitle(Text(store.topic?.name ?? "Загружаем..."))
66-
.navigationBarTitleDisplayMode(.large)
71+
.navigationBarTitleDisplayMode(.inline)
6772
.toolbar { OptionsMenu() }
6873
.onChange(of: store.isLoadingTopic) { _ in
6974
Task { await scrollAndAnimate() }
7075
}
76+
.onChange(of: scenePhase) { newScenePhase in
77+
if (scenePhase == .inactive || scenePhase == .background) && newScenePhase == .active {
78+
store.send(.onSceneBecomeActive)
79+
}
80+
}
7181
.task {
7282
store.send(.onTask)
7383
}

0 commit comments

Comments
 (0)