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
+147
View File
@@ -0,0 +1,147 @@
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
}
}