From 59e5eba860c82412e7a0fe0b2aba7882ced14a96 Mon Sep 17 00:00:00 2001 From: Paul Schuetz Date: Thu, 16 Nov 2023 09:56:00 +0100 Subject: [PATCH] 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 --- .../Env/Sources/Env/CustomEnvValues.swift | 10 +++---- .../Status/Detail/StatusDetailView.swift | 6 ++-- .../Status/Detail/StatusDetailViewModel.swift | 30 ++++++++----------- .../Sources/Status/Row/StatusRowView.swift | 16 ++++++---- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Packages/Env/Sources/Env/CustomEnvValues.swift b/Packages/Env/Sources/Env/CustomEnvValues.swift index ee682808..fa79adce 100644 --- a/Packages/Env/Sources/Env/CustomEnvValues.swift +++ b/Packages/Env/Sources/Env/CustomEnvValues.swift @@ -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 } } } diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift index 25a3ebf2..7500b0f3 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift @@ -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 { diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift index fb53a6f5..eb6888df 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift @@ -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,23 +112,14 @@ 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 - } else { - isReplyToPrevious = true - } + if let inReplyToId = status.inReplyToId, + let prevIndent = indentationLevelPreviousCache[inReplyToId] { + indentationLevelPreviousCache[status.id] = prevIndent + 1 + } else { + indentationLevelPreviousCache[status.id] = 0 } - isReplyToPreviousCache[status.id] = isReplyToPrevious } } @@ -146,4 +138,8 @@ import SwiftUI } } } + + func getIndentationLevel(id: String) -> UInt { + min(indentationLevelPreviousCache[id] ?? 0, Self.maxIndent) + } } diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 86303cf1..2266b571 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -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 { - Rectangle() - .fill(theme.tintColor) - .frame(width: 2) - .accessibilityHidden(true) + HStack(spacing: 3) { + ForEach(0.. 0 { Spacer(minLength: 8) } VStack(alignment: .leading) {