import SwiftUI // MARK: - Заголовок пары /// Иконка монеты + «BTC/USDT» + знак Rapira справа. struct PairHeader: View { let thumb: SymbolThumb let icon: Data? let iconSize: CGFloat let titleSize: CGFloat var body: some View { HStack(spacing: iconSize * 0.3) { CoinIconView(currency: thumb.quoteCurrency, data: icon, size: iconSize) HStack(spacing: 1) { Text(thumb.quoteCurrency) .foregroundStyle( LinearGradient( colors: [.white, .white.opacity(0.85)], startPoint: .leading, endPoint: .trailing ) ) Text("/\(thumb.baseCurrency)") .foregroundStyle(.white.opacity(0.5)) } .font(.system(size: titleSize, weight: .bold)) .lineLimit(1) .minimumScaleFactor(0.6) Spacer(minLength: 4) RapiraMark(size: iconSize * 0.52) } } } // MARK: - Цена /// Крупная цена с приглушённым символом валюты: «70 324,00 USDT». struct PriceLabel: View { let thumb: SymbolThumb let size: CGFloat var body: some View { HStack(alignment: .firstTextBaseline, spacing: size * 0.16) { Text(thumb.priceFormatted) .font(.system(size: size, weight: .bold)) .monospacedDigit() .foregroundStyle( LinearGradient( colors: [.white, .white.opacity(0.7)], startPoint: .leading, endPoint: .trailing ) ) .lineLimit(1) .minimumScaleFactor(0.5) Text(thumb.baseSymbol) .font(.system(size: size * 0.42, weight: .semibold)) .foregroundStyle(.white.opacity(0.5)) } } } // MARK: - Изменение за 24ч /// «+1 676,00 USDT · 2.34%» одной зелёной/красной строкой, без подложки. struct StatsLine: View { let thumb: SymbolThumb let fontSize: CGFloat var showAbsolute = true var body: some View { HStack(spacing: fontSize * 0.4) { if showAbsolute { Text(thumb.changeFormatted) Text("·") .foregroundStyle(RapiraPalette.stat(thumb.isPositive).opacity(0.45)) Text(thumb.changePercentPlain) } else { Text(thumb.changePercent) } } .font(.system(size: fontSize, weight: .semibold)) .monospacedDigit() .foregroundStyle(RapiraPalette.stat(thumb.isPositive)) .lineLimit(1) .minimumScaleFactor(0.7) } } // MARK: - Время обновления /// Подпись «27 октября 17:10» под парой — как в макете. struct UpdatedAt: View { let date: Date let fontSize: CGFloat var withDate = true var body: some View { Text(MarketFormatting.updated(date, withDate: withDate)) .font(.system(size: fontSize, weight: .medium)) .foregroundStyle(.white.opacity(0.5)) .lineLimit(1) } } // MARK: - Пустое состояние struct EmptyState: View { enum Reason { /// Ни одна выбранная пара не найдена в данных. case noSelection /// Ни сети, ни кэша. case noData } let reason: Reason var body: some View { VStack(spacing: 8) { RapiraMark(size: 26) Text(message) .font(.system(size: 11, weight: .medium)) .multilineTextAlignment(.center) .foregroundStyle(.white.opacity(0.45)) } .frame(maxWidth: .infinity, maxHeight: .infinity) } private var message: String { switch reason { case .noSelection: "Выберите пары\nв приложении" case .noData: "Нет данных.\nПроверьте соединение" } } }