feat: v2
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user