Files
rapira-market-widget/crypto/WidgetCards.swift
T
2026-08-12 07:06:01 +07:00

132 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
// MARK: - Карточка-герой
/// Одна пара крупно: заголовок, сквозной график, цена и изменение за 24ч.
struct HeroCard: View {
let quote: Quote
let updatedAt: Date
/// Горизонтальный отступ контейнера график компенсирует его
/// и уходит под края виджета, как в макете.
let horizontalPadding: CGFloat
let iconSize: CGFloat
let titleSize: CGFloat
let priceSize: CGFloat
let chartHeight: CGFloat
var showFullDate = true
var showAbsolute = true
private var thumb: SymbolThumb { quote.thumb }
var body: some View {
VStack(alignment: .leading, spacing: 0) {
PairHeader(thumb: thumb, icon: quote.icon, iconSize: iconSize, titleSize: titleSize)
UpdatedAt(date: updatedAt, fontSize: titleSize * 0.62, withDate: showFullDate)
.padding(.top, titleSize * 0.45)
Spacer(minLength: 6)
chart
.frame(height: chartHeight)
.padding(.horizontal, -horizontalPadding)
Spacer(minLength: 6)
PriceLabel(thumb: thumb, size: priceSize)
StatsLine(thumb: thumb, fontSize: titleSize * 0.78, showAbsolute: showAbsolute)
.padding(.top, titleSize * 0.4)
}
}
@ViewBuilder
private var chart: some View {
if quote.series.count > 1 {
Sparkline(
values: quote.series,
positive: thumb.isPositive,
lineWidth: 2,
pulseSize: iconSize * 0.52
)
} else {
// Нет истории держим ритм макета ровной линией.
Rectangle()
.fill(
LinearGradient(
colors: [
RapiraPalette.lineEdge(thumb.isPositive),
RapiraPalette.line(thumb.isPositive).opacity(0.5),
RapiraPalette.lineEdge(thumb.isPositive),
],
startPoint: .leading, endPoint: .trailing
)
)
.frame(height: 1.5)
.frame(maxHeight: .infinity, alignment: .center)
}
}
}
// MARK: - Строка списка
/// Компактная строка: иконка, пара, мини-график, цена и процент.
struct CryptoRow: View {
let quote: Quote
let compact: Bool
private var thumb: SymbolThumb { quote.thumb }
private var iconSize: CGFloat { compact ? 24 : 26 }
var body: some View {
HStack(spacing: 8) {
CoinIconView(currency: thumb.quoteCurrency, data: quote.icon, size: iconSize)
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 1) {
Text(thumb.quoteCurrency)
.foregroundStyle(.white)
Text("/\(thumb.baseCurrency)")
.foregroundStyle(.white.opacity(0.45))
}
.font(.system(size: compact ? 12 : 13, weight: .bold))
.lineLimit(1)
Text(thumb.quoteCurrencyName)
.font(.system(size: compact ? 9 : 10, weight: .medium))
.foregroundStyle(.white.opacity(0.35))
.lineLimit(1)
}
Spacer(minLength: 4)
if quote.series.count > 1 {
Sparkline(
values: quote.series,
positive: thumb.isPositive,
lineWidth: 1.5,
pulseSize: 7,
areaOpacity: 0.16,
trailingRoom: 0.6
)
.frame(width: compact ? 46 : 56, height: 22)
}
VStack(alignment: .trailing, spacing: 2) {
Text(thumb.priceFormatted)
.font(.system(size: compact ? 12 : 13, weight: .bold))
.monospacedDigit()
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(0.6)
Text(thumb.changePercent)
.font(.system(size: compact ? 10 : 10.5, weight: .semibold))
.monospacedDigit()
.foregroundStyle(RapiraPalette.stat(thumb.isPositive))
}
.frame(minWidth: compact ? 62 : 70, alignment: .trailing)
}
}
}