139 lines
4.1 KiB
Swift
139 lines
4.1 KiB
Swift
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Проверьте соединение"
|
||
}
|
||
}
|
||
}
|