// How every screen that loads data shows it: a static "Loading" before the first answer, the error // and Try again when there is nothing to show, and once content is on screen, a small dim line under // it when a refresh fails. Content is never replaced by an error. import SwiftUI /// The placeholder before a screen's first answer, the same as the feed's. Static, with no spinner. struct LoadingText: View { var body: some View { Text("Loading") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Theme.secondaryText) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 8) .padding(.top, 4) } } /// What a screen shows under its content about loading. `loaded` is true once content is on screen. /// Before then: Loading, or the error with Try again when `retry` is set. After: only a failed /// refresh's error, small and dim. struct LoadStatus: View { var loaded: Bool var error: String? var retry: (() async -> Void)? var body: some View { if let error { if loaded { Text(error) .font(.system(size: 11)) .foregroundStyle(Theme.done) .lineLimit(3) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 8) } else { VStack(alignment: .leading, spacing: 8) { Text(error) .font(.system(size: 12)) .foregroundStyle(Theme.secondaryText) .padding(.horizontal, 8) if let retry { Button("Try again") { Task { await retry() } } .buttonStyle(PillButtonStyle(fill: Theme.control, foreground: .white)) } } .frame(maxWidth: .infinity, alignment: .leading) } } else if !loaded { LoadingText() } } }