Improve the display of replies (#1672)

Threads/replies are now shown more clearly. Each reply has an indentation level
(and therefore the number of vertical lines) one more than its direct parent.
This leads to siblings having the same indentation level. It makes
understanding somewhat complex thread structures way easier. Previously, a
reply was only indented if it came directly after its parent. If a toot had
more than one reply, the structure was nearly indecipherable, as it wasn't
clear which the parent post of the second (or later) toot was. An example is
"https://mastodon.social/@mhoye/110452462852364819" and all of its replies.

Signed-off-by: Paul Schuetz <pa.schuetz@web.de>
This commit is contained in:
Paul Schuetz 2023-11-16 09:56:00 +01:00 committed by GitHub
parent 4b74532048
commit 59e5eba860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 31 deletions

View file

@ -25,8 +25,8 @@ private struct IsStatusFocused: EnvironmentKey {
static let defaultValue: Bool = false
}
private struct IsStatusReplyToPrevious: EnvironmentKey {
static let defaultValue: Bool = false
private struct IndentationLevel: EnvironmentKey {
static let defaultValue: UInt = 0
}
public extension EnvironmentValues {
@ -60,8 +60,8 @@ public extension EnvironmentValues {
set { self[IsStatusFocused.self] = newValue }
}
var isStatusReplyToPrevious: Bool {
get { self[IsStatusReplyToPrevious.self] }
set { self[IsStatusReplyToPrevious.self] = newValue }
var indentationLevel: UInt {
get { self[IndentationLevel.self] }
set { self[IndentationLevel.self] = newValue }
}
}

View file

@ -103,15 +103,15 @@ public struct StatusDetailView: View {
private func makeStatusesListView(statuses: [Status]) -> some View {
ForEach(statuses) { status in
let isReplyToPrevious = viewModel.isReplyToPreviousCache[status.id] ?? false
let indentationLevel = viewModel.getIndentationLevel(id: status.id)
let viewModel: StatusRowViewModel = .init(status: status,
client: client,
routerPath: routerPath)
let isFocused = self.viewModel.statusId == status.id
StatusRowView(viewModel: viewModel)
.environment(\.extraLeadingInset, isReplyToPrevious ? 10 : 0)
.environment(\.isStatusReplyToPrevious, isReplyToPrevious)
.environment(\.extraLeadingInset, (indentationLevel > 0) ? 10 : 0)
.environment(\.indentationLevel, indentationLevel)
.environment(\.isStatusFocused, isFocused)
.overlay {
if isFocused {

View file

@ -19,9 +19,10 @@ import SwiftUI
var state: State = .loading
var title: LocalizedStringKey = ""
var scrollToId: String?
static var maxIndent = UInt(7)
@ObservationIgnored
var isReplyToPreviousCache: [String: Bool] = [:]
var indentationLevelPreviousCache: [String: UInt] = [:]
init(statusId: String) {
state = .loading
@ -35,7 +36,7 @@ import SwiftUI
statusId = status.id
remoteStatusURL = nil
if status.inReplyToId != nil {
isReplyToPreviousCache[status.id] = true
indentationLevelPreviousCache[status.id] = 1
}
}
@ -111,24 +112,15 @@ import SwiftUI
}
private func cacheReplyTopPrevious(statuses: [Status]) {
isReplyToPreviousCache = [:]
indentationLevelPreviousCache = [:]
for status in statuses {
var isReplyToPrevious: Bool = false
if let index = statuses.firstIndex(where: { $0.id == status.id }),
index > 0,
statuses[index - 1].id == status.inReplyToId
{
if index == 1, statuses.count > 2 {
let nextStatus = statuses[2]
isReplyToPrevious = nextStatus.inReplyToId == status.id
} else if statuses.count == 2 {
isReplyToPrevious = false
if let inReplyToId = status.inReplyToId,
let prevIndent = indentationLevelPreviousCache[inReplyToId] {
indentationLevelPreviousCache[status.id] = prevIndent + 1
} else {
isReplyToPrevious = true
indentationLevelPreviousCache[status.id] = 0
}
}
isReplyToPreviousCache[status.id] = isReplyToPrevious
}
}
func handleEvent(event: any StreamEvent, currentAccount: Account?) {
@ -146,4 +138,8 @@ import SwiftUI
}
}
}
func getIndentationLevel(id: String) -> UInt {
min(indentationLevelPreviousCache[id] ?? 0, Self.maxIndent)
}
}

View file

@ -15,7 +15,7 @@ public struct StatusRowView: View {
@Environment(\.isCompact) private var isCompact: Bool
@Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled
@Environment(\.isStatusFocused) private var isFocused
@Environment(\.isStatusReplyToPrevious) private var isStatusReplyToPrevious
@Environment(\.indentationLevel) private var indentationLevel
@Environment(QuickLook.self) private var quickLook
@Environment(Theme.self) private var theme
@ -32,11 +32,15 @@ public struct StatusRowView: View {
public var body: some View {
HStack(spacing: 0) {
if isStatusReplyToPrevious {
HStack(spacing: 3) {
ForEach(0..<indentationLevel, id: \.self) {_ in
Rectangle()
.fill(theme.tintColor)
.frame(width: 2)
.accessibilityHidden(true)
}
}
if indentationLevel > 0 {
Spacer(minLength: 8)
}
VStack(alignment: .leading) {