chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
The iOS app folds a description by how tall it is, not how long
ios/Knotless/Views/ThreadView.swift+30−12
| @@ -1,12 +1,20 @@ | ||
import SwiftUI | ||
| ||
struct ThreadView: View { | ||
/// How much of a long description shows before Read more. | ||
private static let foldedHeight: CGFloat = 360 | ||
/// How much taller than that it must be to fold at all. | ||
private static let foldSlack: CGFloat = 80 | ||
/// As much as is drawn folded, far more than `foldedHeight` holds. | ||
private static let foldedLength = 2000 | ||
| ||
let repo: RepoRef | ||
let uri: String | ||
| ||
@Environment(Account.self) private var account | ||
@State private var screen: CGFloat = 0 | ||
@State private var expanded = false | ||
@State private var bodyHeight: CGFloat = 0 | ||
/// Moved by a write, so the thread is read again as it now stands. | ||
@State private var edits = 0 | ||
@State private var composing = false | ||
| @@ -294,28 +302,38 @@ struct ThreadView: View { | ||
| ||
@ViewBuilder private func body(_ text: String?, html: String?) -> some View { | ||
if let text, !text.isEmpty { | ||
if text.count > 800, !expanded { | ||
MarkdownView(source: String(text.prefix(2000)), html: html, place: MarkdownPlace(repo: repo, subject: uri)) | ||
// Its whole height and then cut, or its text is squeezed to | ||
// fit and truncates, rather than reading as it does opened. | ||
.fixedSize(horizontal: false, vertical: true) | ||
.frame(maxHeight: 360, alignment: .top) | ||
.clipped() | ||
.mask(LinearGradient(stops: [.init(color: .black, location: 0.7), .init(color: .clear, location: 1)], startPoint: .top, endPoint: .bottom)) | ||
.overlay(alignment: .bottom) { | ||
let folded = !expanded && folds(text) | ||
MarkdownView(source: expanded ? text : String(text.prefix(Self.foldedLength)), html: html, place: MarkdownPlace(repo: repo, subject: uri)) | ||
// Its whole height and then cut, or its text is squeezed to | ||
// fit and truncates, rather than reading as it does opened. | ||
.fixedSize(horizontal: false, vertical: true) | ||
.onGeometryChange(for: CGFloat.self, of: \.size.height) { bodyHeight = $0 } | ||
.frame(maxHeight: folded ? Self.foldedHeight : nil, alignment: .top) | ||
.clipped() | ||
.mask(LinearGradient(stops: [ | ||
.init(color: .black, location: folded ? 0.7 : 1), | ||
.init(color: folded ? .clear : .black, location: 1), | ||
], startPoint: .top, endPoint: .bottom)) | ||
.overlay(alignment: .bottom) { | ||
if folded { | ||
Button { expanded = true } label: { | ||
SwiftUI.Label { Text("Read more") } icon: { Oct(name: "arrow-down", size: Icon.md) }.font(.callout.weight(.medium)) | ||
} | ||
.floating() | ||
} | ||
} else { | ||
MarkdownView(source: text, html: html, place: MarkdownPlace(repo: repo, subject: uri)) | ||
} | ||
} | ||
} else { | ||
Text("No description.").foregroundStyle(.secondary) | ||
} | ||
} | ||
| ||
/// Whether folding hides enough to be worth a button. Until it's measured, | ||
/// a guess from its length, so what's going to fold doesn't first draw whole. | ||
private func folds(_ text: String) -> Bool { | ||
if text.count > Self.foldedLength { return true } | ||
ios/Knotless/Views/ThreadView.swiftline 333The length check wins over the measured height here. A body over 2000 characters that draws short still folds and is cut at 2000 characters of source. Consider checking ios/Knotless/Views/ThreadView.swiftline 333This returns true on length alone, before the measured height is read. A long body of links that draws short still folds, and the 2000-character prefix can cut it mid-link. Consider checking | ||
return bodyHeight == 0 ? text.count > 800 : bodyHeight > Self.foldedHeight + Self.foldSlack | ||
} | ||
| ||
@ViewBuilder private func verdict(_ verdict: String?, outdated: Bool) -> some View { | ||
switch verdict { | ||
case "approve": | ||