// One view per complication family, in mock B's colors: amber when something needs you, teal for // work in progress, and nothing that animates. Tinted faces use the accented parts only. import SwiftUI import WidgetKit struct WaitingView: View { @Environment(\.widgetFamily) private var family var entry: WaitingEntry var body: some View { content .widgetURL(next?.route.url) .containerBackground(for: .widget) { if waiting > 0 { LinearGradient(colors: [Theme.you.opacity(0.3), Theme.card], startPoint: .top, endPoint: .bottom) } else { Theme.card } } } @ViewBuilder private var content: some View { switch family { case .accessoryCircular: circular case .accessoryRectangular: rectangular case .accessoryInline: inline case .accessoryCorner: corner default: circular } } // MARK: - Families private var circular: some View { ZStack { AccessoryWidgetBackground() if let summary, summary.needsYou > 0 { VStack(spacing: -3) { Text("\(summary.needsYou)") .font(.system(size: 26, weight: .bold, design: .rounded)) .foregroundStyle(Theme.you) .minimumScaleFactor(0.6) .widgetAccentable() Text("waiting") .font(.system(size: 9, weight: .semibold, design: .rounded)) .foregroundStyle(.secondary) } } else if summary != nil { Image(systemName: "checkmark") .font(.system(size: 20, weight: .semibold)) .foregroundStyle(.secondary) } else { Image(systemName: unpairedSymbol) .font(.system(size: 18, weight: .semibold)) .foregroundStyle(.secondary) } } .accessibilityElement(children: .ignore) .accessibilityLabel(spoken) } private var rectangular: some View { VStack(alignment: .leading, spacing: 0) { if let summary { if let next = summary.next, summary.needsYou > 0 { HStack(alignment: .firstTextBaseline, spacing: 4) { Text("\(summary.needsYou)") .font(.system(size: 22, weight: .bold, design: .rounded)) Text("waiting") .font(.system(size: 14, weight: .semibold, design: .rounded)) Spacer(minLength: 0) staleMark } .foregroundStyle(Theme.you) .widgetAccentable() Text(next.title) .font(.system(size: 15, weight: .semibold, design: .rounded)) .lineLimit(1) Text(next.hostLabel) .font(.system(size: 13, design: .rounded)) .foregroundStyle(.secondary) .lineLimit(1) } else { HStack(spacing: 4) { Image(systemName: "checkmark") Text("Nothing waiting") Spacer(minLength: 0) staleMark } .font(.system(size: 14, weight: .semibold, design: .rounded)) .foregroundStyle(.secondary) Text("\(summary.working) working") .font(.system(size: 17, weight: .bold, design: .rounded)) .foregroundStyle(summary.working > 0 ? Theme.work : .secondary) .widgetAccentable() if summary.hostsOffline > 0 { Text(summary.hostsOffline == 1 ? "1 host offline" : "\(summary.hostsOffline) hosts offline") .font(.system(size: 13, design: .rounded)) .foregroundStyle(.secondary) } } } else { Text("WristCode") .font(.system(size: 15, weight: .bold, design: .rounded)) .foregroundStyle(Theme.work) .widgetAccentable() Text(entry.content == .notPaired ? "Open the app to pair" : "Can't reach the gateway") .font(.system(size: 13, design: .rounded)) .foregroundStyle(.secondary) } } .frame(maxWidth: .infinity, alignment: .leading) .accessibilityElement(children: .combine) } private var inline: some View { ViewThatFits { Text(spoken) if let summary { Text("\(summary.needsYou) waiting") } } } private var corner: some View { Group { if let summary { Text("\(summary.needsYou)") .font(.system(size: 24, weight: .bold, design: .rounded)) .foregroundStyle(summary.needsYou > 0 ? Theme.you : Color.secondary) .widgetAccentable() } else { Image(systemName: unpairedSymbol) .font(.system(size: 18, weight: .semibold)) .foregroundStyle(.secondary) } } .widgetLabel { if let summary { Text(summary.needsYou > 0 ? "waiting" : "\(summary.working) working") } else { Text("WristCode") } } .accessibilityLabel(spoken) } // MARK: - Pieces private var summary: Summary? { if case let .counts(summary, _) = entry.content { return summary } return nil } private var waiting: Int { summary?.needsYou ?? 0 } private var next: Summary.Next? { waiting > 0 ? summary?.next : nil } private var unpairedSymbol: String { entry.content == .notPaired ? "applewatch.slash" : "wifi.slash" } /// Shown beside the count when it is the last one seen, not a fresh one. @ViewBuilder private var staleMark: some View { if case .counts(_, stale: true) = entry.content { Image(systemName: "wifi.slash") .font(.system(size: 11, weight: .semibold)) .foregroundStyle(.secondary) } } /// "2 waiting · 3 working", the inline text and the VoiceOver label. private var spoken: String { switch entry.content { case let .counts(summary, _): "\(summary.needsYou) waiting · \(summary.working) working" case .notPaired: "WristCode not paired" case .unavailable: "WristCode offline" } } } private func entry(_ needsYou: Int, _ working: Int, next: Bool = true) -> WaitingEntry { let thread = Summary.Next(hostId: "mac-mini", threadId: "t-ocr", title: "Receipt OCR", hostLabel: "Mac mini") return WaitingEntry(date: .now, content: .counts( Summary(needsYou: needsYou, working: working, next: needsYou > 0 && next ? thread : nil, hostsOffline: 1), stale: false)) } #Preview("Circular", as: .accessoryCircular) { WaitingWidget() } timeline: { entry(2, 3) entry(0, 3) WaitingEntry(date: .now, content: .notPaired) } #Preview("Rectangular", as: .accessoryRectangular) { WaitingWidget() } timeline: { entry(2, 3) entry(0, 3) WaitingEntry(date: .now, content: .notPaired) } #Preview("Inline", as: .accessoryInline) { WaitingWidget() } timeline: { entry(2, 3) } #Preview("Corner", as: .accessoryCorner) { WaitingWidget() } timeline: { entry(2, 3) entry(0, 3) }