Skip to content

Commit

Permalink
Feed rendering improvements (#53)
Browse files Browse the repository at this point in the history
## Summary

This change improves Feed screen rendering and scrolling performance.

## What was done

- [x] Preserve `StatusReducer.State` when reloading statuses.
- [x] Update status rendering. Only render HTML to AttributedString when
the HTML intended to be displayed changes for the status displayed on
the screen.
  • Loading branch information
darrarski authored Oct 20, 2024
2 parents b7352e9 + 3d8a42b commit 06846fb
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Projects/App/AppFeature/Sources/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct App: SwiftUI.App {
case .feed(.status(.element(_, .view(.linkTapped(_))))): true
case .feed(.status(.element(_, .view(.previewCardTapped)))): true
case .feed(.status(.element(_, .view(.quickLookItemChanged(_))))): true
case .feed(.status(.element(_, .view(.textTask)))): false
case .feed(.status(.element(_, .view(.textSourceChanged)))): false
case .feed(.view(.refreshButtonTapped)): true
case .feed(.view(.refreshTask)): true
case .feed(.view(.seeMoreButtonTapped)): true
Expand Down
8 changes: 6 additions & 2 deletions Projects/App/FeedFeature/Sources/FeedReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ public struct FeedReducer: Reducer, Sendable {
state.isLoading = false
switch result {
case .success(let statuses):
state.statuses = .init(
uniqueElements: statuses.map { StatusReducer.State(status:$0) }
state.statuses = IdentifiedArray(
uniqueElements: statuses.map { status in
var state = state.statuses[id: status.id] ?? StatusReducer.State(status: status)
state.status = status
return state
}
)

case .failure(_):
Expand Down
17 changes: 9 additions & 8 deletions Projects/App/FeedFeature/Sources/StatusReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public struct StatusReducer: Reducer, Sendable {
status.reblog?.value ?? status
}

var textSource: String {
displayStatus.content
}

var attachments: IdentifiedArrayOf<MediaAttachment> {
var attachments = IdentifiedArrayOf<MediaAttachment>()
attachments.append(contentsOf: status.mediaAttachments)
Expand All @@ -50,7 +54,7 @@ public struct StatusReducer: Reducer, Sendable {
case linkTapped(URL)
case previewCardTapped
case quickLookItemChanged(URL?)
case textTask
case textSourceChanged
}
}

Expand All @@ -66,14 +70,14 @@ public struct StatusReducer: Reducer, Sendable {
return .none

case .renderText:
let html = state.displayStatus.content
let html = state.textSource
return .run { send in
let result = Result { try render(html) }
await send(.textRendered(result))
}

case .textRendered(.failure(_)):
state.text = AttributedString(state.displayStatus.content)
state.text = AttributedString(state.textSource)
return .none

case .textRendered(.success(let text)):
Expand Down Expand Up @@ -118,11 +122,8 @@ public struct StatusReducer: Reducer, Sendable {
state.quickLookItem = url
return .none

case .view(.textTask):
if state.text == nil {
return .send(.renderText)
}
return .none
case .view(.textSourceChanged):
return .send(.renderText)
}
}
.ifLet(\.$quickLookItem, action: \.quickLookItem) {
Expand Down
5 changes: 3 additions & 2 deletions Projects/App/FeedFeature/Sources/StatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ public struct StatusView: View {
Text(text)
.redacted(reason: isPlaceholder ? .placeholder : [])
.disabled(isPlaceholder)
.onChange(of: isPlaceholder, initial: true) { _, isPlaceholder in
if isPlaceholder { send(.textTask) }
.onChange(of: store.textSource, initial: true) { old, new in
guard old != new || store.text == nil else { return }
send(.textSourceChanged)
}
.environment(\.openURL, OpenURLAction { url in
send(.linkTapped(url))
Expand Down
30 changes: 30 additions & 0 deletions Projects/App/FeedFeature/Tests/FeedReducerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,34 @@ final class FeedReducerTests: XCTestCase {
FeedReducer.mastodonAccountURL
])
}

@MainActor func testReloadStatusesPreservesTheirStates() async {
let statuses = Array([Status].preview[0...2])
let store = TestStore(initialState: FeedReducer.State(
statuses: [
StatusReducer.State(
status: statuses[0],
text: "Status 0 text"
),
StatusReducer.State(
status: statuses[1],
text: "Status 1 text"
),
StatusReducer.State(
status: statuses[2],
text: "Status 2 text"
),
],
isLoading: true
)) {
FeedReducer()
} withDependencies: {
$0.continuousClock = ImmediateClock()
$0.mastodon.getAccountStatuses.send = { _ in [] }
}

await store.send(.fetchStatusesResult(.success(statuses))) {
$0.isLoading = false
}
}
}
6 changes: 2 additions & 4 deletions Projects/App/FeedFeature/Tests/StatusReducerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,12 @@ final class StatusReducerTests: XCTestCase {
}
}

await store.send(.view(.textTask))
await store.send(.view(.textSourceChanged))
await store.receive(\.renderText)
expectNoDifference(didRender.value, [status.content])
await store.receive(\.textRendered.success) {
$0.text = renderedText
}

await store.send(.view(.textTask))
}

@MainActor func testTextRenderingFailure() async {
Expand All @@ -245,7 +243,7 @@ final class StatusReducerTests: XCTestCase {
$0.statusTextRenderer.render = { _ in throw failure }
}

await store.send(.view(.textTask))
await store.send(.view(.textSourceChanged))
await store.receive(\.renderText)
await store.receive(\.textRendered.failure) {
$0.text = AttributedString(status.content)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ The project is integrated with [Xcode Cloud](https://developer.apple.com/xcode-c

## 🛠 Develop

- Use Xcode (version ≥ 15.4).
- Use Xcode (version ≥ 16.0).
- Clone the repository or create a fork & clone it.
- Run `setup_for_development.sh` script to generate Xcode workspace and set everything up for development:
```sh
Expand Down

0 comments on commit 06846fb

Please sign in to comment.