From 4672cbb151ff571cfc78f1cf89b9e79be8f455c5 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 7 Jan 2025 07:20:54 -0600 Subject: [PATCH] [Paywalls V2] Add support for alias solid hex colors (not gradients) (#4632) * Added alias colors * Fixes from PR review --- .../Components/Stack/StackComponentView.swift | 5 +- .../Stack/StackComponentViewModel.swift | 23 ++++--- .../Components/Text/TextComponentView.swift | 60 ++++++++++++++++++- .../Text/TextComponentViewModel.swift | 2 +- .../Templates/V2/PaywallsV2View.swift | 22 ++++--- .../V2/ViewHelpers/BackgroundStyle.swift | 37 +++++++----- .../ViewHelpers/ForegroundColorScheme.swift | 31 ++++++++-- .../PaywallComponentTypeTransformers.swift | 30 ++++++---- .../ViewModelHelpers/UIConfigProvider.swift | 4 ++ .../ViewModelHelpers/ViewModelFactory.swift | 6 +- 10 files changed, 167 insertions(+), 53 deletions(-) diff --git a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentView.swift b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentView.swift index b839169309..b5f8a2e1c6 100644 --- a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentView.swift +++ b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentView.swift @@ -90,7 +90,7 @@ struct StackComponentView: View { } .padding(style.padding) .padding(additionalPadding) - .backgroundStyle(style.backgroundStyle) + .backgroundStyle(style.backgroundStyle, uiConfigProvider: self.viewModel.uiConfigProvider) .shape(border: style.border, shape: style.shape) .applyIfLet(style.shadow) { view, shadow in @@ -456,7 +456,8 @@ fileprivate extension StackComponentViewModel { try self.init( component: component, - viewModels: viewModels + viewModels: viewModels, + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make()) ) } diff --git a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift index 22ad28f05f..17090710d9 100644 --- a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift +++ b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift @@ -22,16 +22,19 @@ private typealias PresentedStackPartial = PaywallComponent.PartialStackComponent class StackComponentViewModel { private let component: PaywallComponent.StackComponent + let uiConfigProvider: UIConfigProvider private let presentedOverrides: PresentedOverrides? let viewModels: [PaywallComponentViewModel] init( component: PaywallComponent.StackComponent, - viewModels: [PaywallComponentViewModel] + viewModels: [PaywallComponentViewModel], + uiConfigProvider: UIConfigProvider ) throws { self.component = component self.viewModels = viewModels + self.uiConfigProvider = uiConfigProvider self.presentedOverrides = try self.component.overrides?.toPresentedOverrides { $0 } } @@ -51,6 +54,7 @@ class StackComponentViewModel { ) let style = StackComponentStyle( + uiConfigProvider: self.uiConfigProvider, visible: partial?.visible ?? true, dimension: partial?.dimension ?? self.component.dimension, size: partial?.size ?? self.component.size, @@ -107,6 +111,7 @@ struct StackComponentStyle { let shadow: ShadowModifier.ShadowInfo? init( + uiConfigProvider: UIConfigProvider, visible: Bool, dimension: PaywallComponent.Dimension, size: PaywallComponent.Size, @@ -126,8 +131,8 @@ struct StackComponentStyle { self.padding = padding.edgeInsets self.margin = margin.edgeInsets self.shape = shape?.shape - self.border = border?.border - self.shadow = shadow?.shadow + self.border = border?.border(uiConfigProvider: uiConfigProvider) + self.shadow = shadow?.shadow(uiConfigProvider: uiConfigProvider) } var vstackStrategy: StackStrategy { @@ -185,9 +190,9 @@ private extension PaywallComponent.Shape { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallComponent.Border { - var border: ShapeModifier.BorderInfo? { - ShapeModifier.BorderInfo( - color: self.color.toDynamicColor(), + func border(uiConfigProvider: UIConfigProvider) -> ShapeModifier.BorderInfo? { + return ShapeModifier.BorderInfo( + color: self.color.toDynamicColor(uiConfigProvider: uiConfigProvider), width: self.width ) } @@ -197,9 +202,9 @@ private extension PaywallComponent.Border { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallComponent.Shadow { - var shadow: ShadowModifier.ShadowInfo? { - ShadowModifier.ShadowInfo( - color: self.color.toDynamicColor(), + func shadow(uiConfigProvider: UIConfigProvider) -> ShadowModifier.ShadowInfo? { + return ShadowModifier.ShadowInfo( + color: self.color.toDynamicColor(uiConfigProvider: uiConfigProvider), radius: self.radius, x: self.x, y: self.y diff --git a/RevenueCatUI/Templates/V2/Components/Text/TextComponentView.swift b/RevenueCatUI/Templates/V2/Components/Text/TextComponentView.swift index 548aea969c..db579d81ee 100644 --- a/RevenueCatUI/Templates/V2/Components/Text/TextComponentView.swift +++ b/RevenueCatUI/Templates/V2/Components/Text/TextComponentView.swift @@ -11,6 +11,8 @@ // // Created by Josh Holtz on 6/11/24. +// swiftlint:disable file_length + import Foundation import RevenueCat import SwiftUI @@ -54,10 +56,10 @@ struct TextComponentView: View { .fontWeight(style.fontWeight) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(style.textAlignment) - .foregroundColorScheme(style.color) + .foregroundColorScheme(style.color, uiConfigProvider: self.viewModel.uiConfigProvider) .padding(style.padding) .size(style.size, alignment: style.horizontalAlignment) - .backgroundStyle(style.backgroundStyle) + .backgroundStyle(style.backgroundStyle, uiConfigProvider: self.viewModel.uiConfigProvider) .padding(style.margin) } else { EmptyView() @@ -189,6 +191,60 @@ struct TextComponentView_Previews: PreviewProvider { .previewLayout(.sizeThatFits) .previewDisplayName("Custom Font") + // Custom Color + VStack { + TextComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizationProvider: .init( + locale: Locale.current, + localizedStrings: [ + "id_1": .string("Red bg, yellow fg") + ] + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make( + colors: [ + "primary": .hex("#ff0000"), + "secondary": .hex("#ffcc00") + ] + )), + component: .init( + text: "id_1", + color: .init(light: .alias("secondary")), + backgroundColor: .init(light: .alias("primary")), + fontSize: .headingXXL + ) + ) + ) + + TextComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizationProvider: .init( + locale: Locale.current, + localizedStrings: [ + "id_1": .string("Clear bg and default fg") + ] + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make( + colors: [ + "primary": .hex("#ff0000"), + "secondary": .hex("#ffcc00") + ] + )), + component: .init( + text: "id_1", + color: .init(light: .alias("not a thing")), + backgroundColor: .init(light: .alias("also not a thing")), + fontSize: .headingXXL + ) + ) + ) + } + .previewRequiredEnvironmentProperties() + .previewLayout(.sizeThatFits) + .previewDisplayName("Custom Color") + // Gradient TextComponentView( // swiftlint:disable:next force_try diff --git a/RevenueCatUI/Templates/V2/Components/Text/TextComponentViewModel.swift b/RevenueCatUI/Templates/V2/Components/Text/TextComponentViewModel.swift index 92ecaf11e7..c43fd2ece2 100644 --- a/RevenueCatUI/Templates/V2/Components/Text/TextComponentViewModel.swift +++ b/RevenueCatUI/Templates/V2/Components/Text/TextComponentViewModel.swift @@ -20,7 +20,7 @@ import SwiftUI class TextComponentViewModel { private let localizationProvider: LocalizationProvider - private let uiConfigProvider: UIConfigProvider + let uiConfigProvider: UIConfigProvider private let component: PaywallComponent.TextComponent private let text: String diff --git a/RevenueCatUI/Templates/V2/PaywallsV2View.swift b/RevenueCatUI/Templates/V2/PaywallsV2View.swift index 93203fc195..bcd4ec9bfc 100644 --- a/RevenueCatUI/Templates/V2/PaywallsV2View.swift +++ b/RevenueCatUI/Templates/V2/PaywallsV2View.swift @@ -50,7 +50,7 @@ struct PaywallsV2View: View { private var paywallStateManager: PaywallStateManager private let paywallComponentsData: PaywallComponentsData - private let uiConfig: UIConfig + private let uiConfigProvider: UIConfigProvider private let offering: Offering private let onDismiss: () -> Void @@ -61,8 +61,10 @@ struct PaywallsV2View: View { showZeroDecimalPlacePrices: Bool, onDismiss: @escaping () -> Void ) { + let uiConfigProvider = UIConfigProvider(uiConfig: paywallComponents.uiConfig) + self.paywallComponentsData = paywallComponents.data - self.uiConfig = paywallComponents.uiConfig + self.uiConfigProvider = uiConfigProvider self.offering = offering self.onDismiss = onDismiss self._introOfferEligibilityContext = .init( @@ -80,7 +82,7 @@ struct PaywallsV2View: View { componentsConfig: componentsConfig, componentsLocalizations: paywallComponents.data.componentsLocalizations, defaultLocale: paywallComponents.data.defaultLocale, - uiConfig: paywallComponents.uiConfig, + uiConfigProvider: uiConfigProvider, offering: offering, introEligibilityChecker: introEligibilityChecker, showZeroDecimalPlacePrices: showZeroDecimalPlacePrices @@ -93,6 +95,7 @@ struct PaywallsV2View: View { case .success(let paywallState): LoadedPaywallsV2View( paywallState: paywallState, + uiConfigProvider: self.uiConfigProvider, onDismiss: self.onDismiss ) .environment(\.screenCondition, ScreenCondition.from(self.horizontalSizeClass)) @@ -135,13 +138,15 @@ struct PaywallsV2View: View { private struct LoadedPaywallsV2View: View { private let paywallState: PaywallState + private let uiConfigProvider: UIConfigProvider private let onDismiss: () -> Void @StateObject private var selectedPackageContext: PackageContext - init(paywallState: PaywallState, onDismiss: @escaping () -> Void) { + init(paywallState: PaywallState, uiConfigProvider: UIConfigProvider, onDismiss: @escaping () -> Void) { self.paywallState = paywallState + self.uiConfigProvider = uiConfigProvider self.onDismiss = onDismiss self._selectedPackageContext = .init( @@ -164,7 +169,10 @@ private struct LoadedPaywallsV2View: View { } .environmentObject(self.selectedPackageContext) .frame(maxHeight: .infinity, alignment: .topLeading) - .backgroundStyle(self.paywallState.componentsConfig.background.backgroundStyle) + .backgroundStyle( + self.paywallState.componentsConfig.background.backgroundStyle, + uiConfigProvider: self.uiConfigProvider + ) .edgesIgnoringSafeArea(.top) } @@ -178,7 +186,7 @@ fileprivate extension PaywallsV2View { componentsConfig: PaywallComponentsData.PaywallComponentsConfig, componentsLocalizations: [PaywallComponent.LocaleID: PaywallComponent.LocalizationDictionary], defaultLocale: String, - uiConfig: UIConfig, + uiConfigProvider: UIConfigProvider, offering: Offering, introEligibilityChecker: TrialOrIntroEligibilityChecker, showZeroDecimalPlacePrices: Bool @@ -190,8 +198,6 @@ fileprivate extension PaywallsV2View { ) do { - let uiConfigProvider = UIConfigProvider(uiConfig: uiConfig) - let factory = ViewModelFactory() let root = try factory.toRootViewModel( componentsConfig: componentsConfig, diff --git a/RevenueCatUI/Templates/V2/ViewHelpers/BackgroundStyle.swift b/RevenueCatUI/Templates/V2/ViewHelpers/BackgroundStyle.swift index bdfb0d32ac..185c372bae 100644 --- a/RevenueCatUI/Templates/V2/ViewHelpers/BackgroundStyle.swift +++ b/RevenueCatUI/Templates/V2/ViewHelpers/BackgroundStyle.swift @@ -30,11 +30,12 @@ struct BackgroundStyleModifier: ViewModifier { var colorScheme var backgroundStyle: BackgroundStyle? + var uiConfigProvider: UIConfigProvider func body(content: Content) -> some View { if let backgroundStyle { content - .apply(backgroundStyle: backgroundStyle, colorScheme: colorScheme) + .apply(backgroundStyle: backgroundStyle, colorScheme: colorScheme, uiConfigProvider: uiConfigProvider) } else { content } @@ -46,12 +47,16 @@ struct BackgroundStyleModifier: ViewModifier { fileprivate extension View { @ViewBuilder - func apply(backgroundStyle: BackgroundStyle, colorScheme: ColorScheme) -> some View { + func apply( + backgroundStyle: BackgroundStyle, + colorScheme: ColorScheme, + uiConfigProvider: UIConfigProvider + ) -> some View { switch backgroundStyle { case .color(let color): switch color.effectiveColor(for: colorScheme) { case .hex, .alias: - self.background(color.toDynamicColor()) + self.background(color.toDynamicColor(uiConfigProvider: uiConfigProvider)) case .linear(let degrees, _): self.background { GradientView( @@ -91,8 +96,8 @@ fileprivate extension View { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) extension View { - func backgroundStyle(_ backgroundStyle: BackgroundStyle?) -> some View { - self.modifier(BackgroundStyleModifier(backgroundStyle: backgroundStyle)) + func backgroundStyle(_ backgroundStyle: BackgroundStyle?, uiConfigProvider: UIConfigProvider) -> some View { + self.modifier(BackgroundStyleModifier(backgroundStyle: backgroundStyle, uiConfigProvider: uiConfigProvider)) } } @@ -143,7 +148,7 @@ struct BackgrounDStyle_Previews: PreviewProvider { .backgroundStyle(.color(.init( light: .hex("#ff0000"), dark: .hex("#ffcc00") - ))) + )), uiConfigProvider: .init(uiConfig: PreviewUIConfig.make())) .previewLayout(.sizeThatFits) .previewDisplayName("Color - Light (should be red)") @@ -152,7 +157,7 @@ struct BackgrounDStyle_Previews: PreviewProvider { .backgroundStyle(.color(.init( light: .hex("#ff0000"), dark: .hex("#ffcc00") - ))) + )), uiConfigProvider: .init(uiConfig: PreviewUIConfig.make())) .preferredColorScheme(.dark) .previewLayout(.sizeThatFits) .previewDisplayName("Color - Dark (should be yellow)") @@ -161,7 +166,7 @@ struct BackgrounDStyle_Previews: PreviewProvider { testContent .backgroundStyle(.color(.init( light: .hex("#ff0000") - ))) + )), uiConfigProvider: .init(uiConfig: PreviewUIConfig.make())) .preferredColorScheme(.dark) .previewLayout(.sizeThatFits) .previewDisplayName("Color - Dark (should be red because fallback)") @@ -183,7 +188,7 @@ struct BackgrounDStyle_Previews: PreviewProvider { heic: darkUrl, heicLowRes: darkUrl ) - ))) + )), uiConfigProvider: .init(uiConfig: PreviewUIConfig.make())) .previewLayout(.sizeThatFits) .previewDisplayName("Image - Light (should be pink cat)") @@ -204,7 +209,7 @@ struct BackgrounDStyle_Previews: PreviewProvider { heic: darkUrl, heicLowRes: darkUrl ) - ))) + )), uiConfigProvider: .init(uiConfig: PreviewUIConfig.make())) .preferredColorScheme(.dark) .previewLayout(.sizeThatFits) .previewDisplayName("Image - Dark (should be japan cats)") @@ -222,7 +227,8 @@ struct BackgrounDStyle_Previews: PreviewProvider { .init(color: "#E58984", percent: 100) ]) ) - ) + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make()) ) .preferredColorScheme(.dark) .previewLayout(.sizeThatFits) @@ -241,7 +247,8 @@ struct BackgrounDStyle_Previews: PreviewProvider { .init(color: "#9DEAD3", percent: 100) ]) ) - ) + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make()) ) .previewLayout(.sizeThatFits) .previewDisplayName("Linear Gradient - Light (should be green") @@ -259,7 +266,8 @@ struct BackgrounDStyle_Previews: PreviewProvider { .init(color: "#E58984", percent: 100) ]) ) - ) + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make()) ) .preferredColorScheme(.dark) .previewLayout(.sizeThatFits) @@ -279,7 +287,8 @@ struct BackgrounDStyle_Previews: PreviewProvider { .init(color: "#ffffff", percent: 100) ]) ) - ) + ), + uiConfigProvider: .init(uiConfig: PreviewUIConfig.make()) ) .previewLayout(.sizeThatFits) .previewDisplayName("Radial Gradient - Light (should be green") diff --git a/RevenueCatUI/Templates/V2/ViewHelpers/ForegroundColorScheme.swift b/RevenueCatUI/Templates/V2/ViewHelpers/ForegroundColorScheme.swift index 4198578931..54d1e4a4c8 100644 --- a/RevenueCatUI/Templates/V2/ViewHelpers/ForegroundColorScheme.swift +++ b/RevenueCatUI/Templates/V2/ViewHelpers/ForegroundColorScheme.swift @@ -23,27 +23,48 @@ struct ForegroundColorSchemeModifier: ViewModifier { var colorScheme var foregroundColorScheme: PaywallComponent.ColorScheme + var uiConfigProvider: UIConfigProvider func body(content: Content) -> some View { - content.foregroundColorScheme(foregroundColorScheme, colorScheme: colorScheme) + content.foregroundColorScheme( + self.foregroundColorScheme, + colorScheme: self.colorScheme, + uiConfigProvider: self.uiConfigProvider + ) } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) extension View { - func foregroundColorScheme(_ colorScheme: PaywallComponent.ColorScheme) -> some View { - self.modifier(ForegroundColorSchemeModifier(foregroundColorScheme: colorScheme)) + func foregroundColorScheme( + _ colorScheme: PaywallComponent.ColorScheme, + uiConfigProvider: UIConfigProvider + ) -> some View { + self.modifier(ForegroundColorSchemeModifier(foregroundColorScheme: colorScheme, + uiConfigProvider: uiConfigProvider)) } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) fileprivate extension View { @ViewBuilder - func foregroundColorScheme(_ color: PaywallComponent.ColorScheme, colorScheme: ColorScheme) -> some View { + func foregroundColorScheme( + _ color: PaywallComponent.ColorScheme, + colorScheme: ColorScheme, + uiConfigProvider: UIConfigProvider + ) -> some View { switch color.effectiveColor(for: colorScheme) { case .hex, .alias: - self.foregroundColor(color.toDynamicColor()) + let color = color.toDynamicColor(uiConfigProvider: uiConfigProvider) + + // Do not apply a clear text color + // Use the default color + if color != Color.clear { + self.foregroundColor(color) + } else { + self + } case .linear(let degrees, _): self.overlay { GradientView( diff --git a/RevenueCatUI/Templates/V2/ViewModelHelpers/PaywallComponentTypeTransformers.swift b/RevenueCatUI/Templates/V2/ViewModelHelpers/PaywallComponentTypeTransformers.swift index 0c883546f3..00dc401ae8 100644 --- a/RevenueCatUI/Templates/V2/ViewModelHelpers/PaywallComponentTypeTransformers.swift +++ b/RevenueCatUI/Templates/V2/ViewModelHelpers/PaywallComponentTypeTransformers.swift @@ -58,7 +58,6 @@ extension PaywallComponent.FontSize { if let customFont = UIFont(name: familyName, size: fontSize) { baseFont = customFont } else { - // Log a warning about the missing custom font Logger.warning("Custom font '\(familyName)' could not be loaded. Falling back to system font.") baseFont = UIFont.systemFont(ofSize: fontSize, weight: .regular) } @@ -239,13 +238,24 @@ extension PaywallComponent.FitMode { extension PaywallComponent.ColorInfo { - func toColor(fallback: Color) -> Color { + func toColor(fallback: Color, uiConfigProvider: UIConfigProvider) -> Color { switch self { case .hex(let hex): return hex.toColor(fallback: fallback) - case .alias: - // WIP: Need to implement this when we actually have alias implemented - return fallback + case .alias(let alias): + guard let aliasColor = uiConfigProvider.getColor(for: alias) else { + Logger.warning("Aliased color '\(alias)' does not exist.") + return fallback + } + + // Alias should never have an alias + // Using fallback so recursion doesn't happen + if case .alias = aliasColor { + Logger.warning("Aliased color '\(alias)' has an aliased value which is not allowed.") + return fallback + } + + return aliasColor.toColor(fallback: fallback, uiConfigProvider: uiConfigProvider) case .linear, .radial: return fallback } @@ -313,10 +323,10 @@ extension PaywallComponent.ColorHex { extension PaywallComponent.ColorScheme { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) - func toDynamicColor() -> Color { + func toDynamicColor(uiConfigProvider: UIConfigProvider) -> Color { guard let darkModeColor = self.dark else { - return light.toColor(fallback: Color.clear) + return light.toColor(fallback: Color.clear, uiConfigProvider: uiConfigProvider) } let lightModeColor = light @@ -324,11 +334,11 @@ extension PaywallComponent.ColorScheme { return Color(UIColor { traitCollection in switch traitCollection.userInterfaceStyle { case .light, .unspecified: - return UIColor(lightModeColor.toColor(fallback: Color.clear)) + return UIColor(lightModeColor.toColor(fallback: Color.clear, uiConfigProvider: uiConfigProvider)) case .dark: - return UIColor(darkModeColor.toColor(fallback: Color.clear)) + return UIColor(darkModeColor.toColor(fallback: Color.clear, uiConfigProvider: uiConfigProvider)) @unknown default: - return UIColor(lightModeColor.toColor(fallback: Color.clear)) + return UIColor(lightModeColor.toColor(fallback: Color.clear, uiConfigProvider: uiConfigProvider)) } }) } diff --git a/RevenueCatUI/Templates/V2/ViewModelHelpers/UIConfigProvider.swift b/RevenueCatUI/Templates/V2/ViewModelHelpers/UIConfigProvider.swift index df069b4b2f..c98f2e5f14 100644 --- a/RevenueCatUI/Templates/V2/ViewModelHelpers/UIConfigProvider.swift +++ b/RevenueCatUI/Templates/V2/ViewModelHelpers/UIConfigProvider.swift @@ -24,6 +24,10 @@ struct UIConfigProvider { self.uiConfig = uiConfig } + func getColor(for name: String) -> PaywallComponent.ColorInfo? { + return self.uiConfig.app.colors[name] + } + func getFontFamily(for name: String?) -> String? { guard let name, let fontInfo = self.uiConfig.app.fonts[name]?.ios else { return nil diff --git a/RevenueCatUI/Templates/V2/ViewModelHelpers/ViewModelFactory.swift b/RevenueCatUI/Templates/V2/ViewModelHelpers/ViewModelFactory.swift index 4d7bc6d08f..2988f4ff90 100644 --- a/RevenueCatUI/Templates/V2/ViewModelHelpers/ViewModelFactory.swift +++ b/RevenueCatUI/Templates/V2/ViewModelHelpers/ViewModelFactory.swift @@ -92,7 +92,8 @@ struct ViewModelFactory { return .stack( try StackComponentViewModel(component: component, - viewModels: viewModels) + viewModels: viewModels, + uiConfigProvider: uiConfigProvider) ) case .button(let component): let stackViewModel = try toStackViewModel( @@ -175,7 +176,8 @@ struct ViewModelFactory { return try StackComponentViewModel( component: component, - viewModels: viewModels + viewModels: viewModels, + uiConfigProvider: uiConfigProvider ) }