import WidgetKit import SwiftUI // MARK: - Маленький /// Герой-карточка первой пары в сжатой компоновке. struct SmallWidgetView: View { let entry: CryptoEntry private let padding: CGFloat = 14 var body: some View { Group { if let hero = entry.quotes.first { HeroCard( quote: hero, updatedAt: entry.date, horizontalPadding: padding, iconSize: 24, titleSize: 13, priceSize: 21, chartHeight: 38, showFullDate: false, showAbsolute: false ) .padding(padding) } else { EmptyState(reason: entry.emptyReason) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) .containerBackground(for: .widget) { RapiraBackground() } } } // MARK: - Средний /// Список до трёх пар со временем обновления в шапке. struct MediumWidgetView: View { let entry: CryptoEntry var body: some View { Group { if entry.quotes.isEmpty { EmptyState(reason: entry.emptyReason) } else { list } } .containerBackground(for: .widget) { RapiraBackground() } } private var list: some View { VStack(spacing: 0) { HStack { UpdatedAt(date: entry.date, fontSize: 9.5) Spacer() RapiraMark(size: 13) } .padding(.bottom, 2) let quotes = Array(entry.quotes.prefix(3)) ForEach(Array(quotes.enumerated()), id: \.element.id) { index, quote in CryptoRow(quote: quote, compact: true) .frame(maxHeight: .infinity) if index < quotes.count - 1 { RapiraPalette.divider.frame(height: 1).padding(.leading, 32) } } } .padding(.horizontal, 14) .padding(.vertical, 8) } } // MARK: - Большой /// Герой первой пары + до четырёх остальных списком. struct LargeWidgetView: View { let entry: CryptoEntry private let padding: CGFloat = 16 var body: some View { Group { if let hero = entry.quotes.first { content(hero: hero, rest: Array(entry.quotes.dropFirst().prefix(4))) } else { EmptyState(reason: entry.emptyReason) } } .containerBackground(for: .widget) { RapiraBackground() } } private func content(hero: Quote, rest: [Quote]) -> some View { VStack(alignment: .leading, spacing: 0) { HeroCard( quote: hero, updatedAt: entry.date, horizontalPadding: padding, iconSize: 36, titleSize: 19, priceSize: 32, chartHeight: 72 ) if !rest.isEmpty { RapiraPalette.divider .frame(height: 1) .padding(.top, 14) .padding(.horizontal, -padding) ForEach(Array(rest.enumerated()), id: \.element.id) { index, quote in CryptoRow(quote: quote, compact: false) .frame(maxHeight: .infinity) if index < rest.count - 1 { RapiraPalette.divider.frame(height: 1).padding(.leading, 34) } } } } .padding(padding) } } // MARK: - Диспетчер размеров struct CryptoWidgetEntryView: View { let entry: CryptoEntry @Environment(\.widgetFamily) private var family var body: some View { switch family { case .systemSmall: SmallWidgetView(entry: entry) case .systemMedium: MediumWidgetView(entry: entry) case .systemLarge: LargeWidgetView(entry: entry) default: MediumWidgetView(entry: entry) } } } extension CryptoEntry { var emptyReason: EmptyState.Reason { dataUnavailable ? .noData : .noSelection } }