This commit is contained in:
2026-08-12 07:06:01 +07:00
parent 1025658518
commit 9bc96dbc2d
39 changed files with 1682 additions and 658 deletions
+131
View File
@@ -0,0 +1,131 @@
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)
}
}
}