@@ -3,6 +3,7 @@ import Charts
33
44struct ChartCard : View {
55 @ObservedObject private var viewModel : ChartCardViewModel
6+ @Environment ( \. context) private var context
67
78 private var dateRange : StatsDateRange { viewModel. effectiveDateRange }
89 private var metrics : [ SiteMetric ] { viewModel. metrics }
@@ -11,20 +12,20 @@ struct ChartCard: View {
1112
1213 @State private var isShowingRawData = false
1314
14- @ScaledMetric ( relativeTo: . largeTitle) private var chartHeight = 180
15+ @ScaledMetric ( relativeTo: . largeTitle) private var chartHeight = 140
1516
1617 init ( viewModel: ChartCardViewModel ) {
1718 self . viewModel = viewModel
1819 }
1920
2021 var body : some View {
2122 VStack ( spacing: 0 ) {
22- VStack ( spacing: Constants . step1 ) {
23+ VStack ( spacing: Constants . step0_5 ) {
2324 headerView ( for: selectedMetric)
2425 . unredacted ( )
2526 contentView
2627 }
27- . padding ( . vertical, Constants . step2 )
28+ . padding ( . vertical, 14 )
2829 . padding ( . horizontal, Constants . step3)
2930 . contentShape ( Rectangle ( ) )
3031 . onTapGesture {
@@ -69,22 +70,29 @@ struct ChartCard: View {
6970 }
7071 }
7172
73+ private func makeHeaderViewModel( for metric: SiteMetric ) -> ChartCardHeaderView . ViewModel {
74+ let data = viewModel. chartData [ selectedMetric] ?? mockChartData
75+ return ChartCardHeaderView . ViewModel (
76+ trend: viewModel. selectedBarTrend ?? . make( data, context: . regular) ,
77+ metricTitle: metric. localizedTitle,
78+ period: context. formatters. dateRange. string ( from: viewModel. dateRange. subrange ?? viewModel. dateRange. range) ,
79+ showComparison: dateRange. comparison != . off
80+ )
81+ }
82+
7283 private func headerView( for metric: SiteMetric ) -> some View {
73- HStack ( alignment : . center ) {
74- StatsCardTitleView ( title : metric . localizedTitle )
75- Spacer ( minLength : 0 )
76- }
77- . accessibilityElement ( children: . combine)
78- . accessibilityLabel ( Strings . Accessibility. cardTitle ( metric. localizedTitle) )
84+ ChartCardHeaderView ( viewModel : makeHeaderViewModel ( for : metric ) )
85+ . redacted ( reason : viewModel . isFirstLoad ? . placeholder : [ ] )
86+ // Leave room for the "more" menu overlay (50pt button, 24pt card padding = 26pt overlap )
87+ . padding ( . trailing , Constants . step3 + Constants . step0_5 )
88+ . accessibilityElement ( children: . combine)
89+ . accessibilityLabel ( Strings . Accessibility. cardTitle ( metric. localizedTitle) )
7990 }
8091
8192 @ViewBuilder
8293 private var contentView : some View {
83- VStack ( spacing: Constants . step1) {
84- if dateRange. comparison != . off || metrics. count == 1 {
85- chartHeaderView
86- . padding ( . trailing, - Constants. step0_5)
87- }
94+ // warning: important to put `chartContentView` in a container in order for animations to work properly. Do NOT remove the container.
95+ HStack {
8896 chartContentView
8997 }
9098 . environment ( \. showComparison, dateRange. comparison != . off)
@@ -93,33 +101,6 @@ struct ChartCard: View {
93101 . animation ( . easeInOut, value: viewModel. isFirstLoad)
94102 }
95103
96- private var chartHeaderView : some View {
97- // Showing currently selected (not loaded period) by design
98- HStack ( alignment: . center, spacing: 0 ) {
99- if let data = viewModel. chartData [ selectedMetric] {
100- ChartValuesSummaryView (
101- trend: viewModel. selectedBarTrend ?? . make( data, context: . regular) ,
102- style: . compact
103- )
104- } else if viewModel. isFirstLoad {
105- ChartValuesSummaryView (
106- trend: . init( currentValue: 100 , previousValue: 10 , metric: SiteMetric . views) ,
107- style: . compact
108- )
109- . redacted ( reason: . placeholder)
110- }
111-
112- Spacer ( minLength: 8 )
113-
114- ChartLegendView (
115- metric: selectedMetric,
116- currentPeriod: viewModel. dateRange. subrange? . dateInterval ?? dateRange. dateInterval,
117- previousPeriod: viewModel. dateRange. subrange? . effectiveComparisonInterval ?? dateRange. effectiveComparisonInterval
118- )
119- }
120- . dynamicTypeSize ( ... DynamicTypeSize . xxLarge)
121- }
122-
123104 @ViewBuilder
124105 private var chartContentView : some View {
125106 if viewModel. isFirstLoad {
@@ -291,7 +272,7 @@ private struct CardGradientBackground: View {
291272 LinearGradient (
292273 colors: [
293274 metric. primaryColor. opacity ( colorScheme == . light ? 0.03 : 0.04 ) ,
294- Constants . Colors. secondaryBackground
275+ Constants . Colors. secondaryBackground,
295276 ] ,
296277 startPoint: . top,
297278 endPoint: . center
@@ -320,6 +301,48 @@ public enum ChartType: String, CaseIterable, Identifiable, Codable {
320301 }
321302}
322303
304+ // MARK: - ChartCardHeaderView
305+
306+ struct ChartCardHeaderView : View {
307+ struct ViewModel : Equatable {
308+ let trend : TrendViewModel
309+ let metricTitle : String
310+ let period : String
311+ let showComparison : Bool
312+ }
313+
314+ let viewModel : ViewModel
315+
316+ var body : some View {
317+ HStack {
318+ VStack ( alignment: . leading, spacing: - 1 ) {
319+ HStack ( alignment: . lastTextBaseline, spacing: 3 ) {
320+ Text ( viewModel. trend. formattedCurrentValue)
321+ . font ( . system( . title2, design: . rounded, weight: . semibold) )
322+ . kerning ( - 0.5 )
323+ . foregroundColor ( . primary)
324+ . contentTransition ( . numericText( ) )
325+ Text ( viewModel. metricTitle)
326+ . font ( . caption. weight ( . medium) )
327+ . foregroundColor ( . secondary)
328+ }
329+ Text ( viewModel. period)
330+ . font ( . system( . caption, design: . rounded, weight: . medium) )
331+ . foregroundStyle ( Color . secondary)
332+ if viewModel. showComparison {
333+ Text ( " \( viewModel. trend. formattedChange) \( viewModel. trend. iconSign) \( viewModel. trend. formattedPercentage) " )
334+ . font ( . caption. weight ( . semibold) )
335+ . foregroundColor ( viewModel. trend. sentiment. foregroundColor)
336+ . contentTransition ( . numericText( ) )
337+ . padding ( . top, 5 )
338+ }
339+ }
340+ Spacer ( minLength: 0 )
341+ }
342+ . animation ( . spring, value: viewModel)
343+ }
344+ }
345+
323346private struct ChartCardPreview : View {
324347 @StateObject var viewModel = ChartCardViewModel (
325348 configuration: ChartCardConfiguration (
0 commit comments