00962fe4a3
App (App/): - TimelineView: NavigationStack + List, pull-to-refresh, compose button - PostRowView: avatar (AsyncImage + initials), nick, relative time, lang badge, post type (reply/boost), body text, tags; Org Mode links stripped - ComposeView: TextEditor, lang/tags fields, publishes to test vhost - TimelineViewModel: @Observable @MainActor, loads timeline from relay - ComposeViewModel: @Observable @MainActor, fetches + appends + uploads post - AvatarView: async image with initials fallback Infrastructure: - project.yml for xcodegen (references OrgSocialKit as local package) - AccentColor: organic green (#649855) Parser fix: - Accept Z timezone in RFC 3339 timestamps (generateTimestamp() on UTC hosts) Test improvements: - MockURLProtocol rewritten with URL-keyed response map (no global handler races) - All suites wrapped in outer @Suite(.serialized) to prevent cross-suite races
41 lines
1015 B
Swift
41 lines
1015 B
Swift
import SwiftUI
|
|
|
|
struct AvatarView: View {
|
|
let url: URL?
|
|
let nick: String?
|
|
var size: CGFloat = 40
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let url {
|
|
AsyncImage(url: url) { phase in
|
|
switch phase {
|
|
case .success(let image):
|
|
image.resizable().scaledToFill()
|
|
default:
|
|
placeholder
|
|
}
|
|
}
|
|
} else {
|
|
placeholder
|
|
}
|
|
}
|
|
.frame(width: size, height: size)
|
|
.clipShape(Circle())
|
|
}
|
|
|
|
private var placeholder: some View {
|
|
Circle()
|
|
.fill(Color.accentColor.opacity(0.15))
|
|
.overlay(
|
|
Text(initial)
|
|
.font(.system(size: size * 0.4, weight: .semibold))
|
|
.foregroundStyle(Color.accentColor)
|
|
)
|
|
}
|
|
|
|
private var initial: String {
|
|
nick?.prefix(1).uppercased() ?? "?"
|
|
}
|
|
}
|