diff --git a/RevenueCatUI/Templates/Components/Image/ImageComponentView.swift b/RevenueCatUI/Templates/Components/Image/ImageComponentView.swift index c217e8d944..e13d82da53 100644 --- a/RevenueCatUI/Templates/Components/Image/ImageComponentView.swift +++ b/RevenueCatUI/Templates/Components/Image/ImageComponentView.swift @@ -20,29 +20,40 @@ import SwiftUI @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct ImageComponentView: View { + @Environment(\.componentViewState) + private var componentViewState + + @Environment(\.screenCondition) + private var screenCondition + let viewModel: ImageComponentViewModel var body: some View { - RemoteImage(url: viewModel.url) { (image, size) in - renderImage(image, size) + viewModel.styles( + state: self.componentViewState, + condition: self.screenCondition + ) { style in + RemoteImage(url: style.url) { (image, size) in + renderImage(image, size, with: style) + } + .size(style.size) + .clipped() } - .size(viewModel.size) - .clipped() } - private func renderImage(_ image: Image, _ size: CGSize) -> some View { + private func renderImage(_ image: Image, _ size: CGSize, with style: ImageComponentStyle) -> some View { image .resizable() - .aspectRatio(contentMode: viewModel.contentMode) + .aspectRatio(contentMode: style.contentMode) .overlay( LinearGradient( - gradient: Gradient(colors: viewModel.gradientColors), + gradient: Gradient(colors: style.gradientColors), startPoint: .top, endPoint: .bottom ) ) .shape(border: nil, - shape: viewModel.shape) + shape: style.shape) } } diff --git a/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift b/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift index 4ac0119fe3..6eb0bf2f2b 100644 --- a/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift @@ -24,6 +24,7 @@ class ImageComponentViewModel { private let component: PaywallComponent.ImageComponent private let imageInfo: PaywallComponent.ThemeImageUrls + private let presentedOverrides: PresentedOverrides? init(localizedStrings: PaywallComponent.LocalizationDictionary, component: PaywallComponent.ImageComponent) throws { self.localizedStrings = localizedStrings @@ -34,22 +35,113 @@ class ImageComponentViewModel { } else { self.imageInfo = component.source } + + self.presentedOverrides = try self.component.overrides?.toPresentedOverrides { + try LocalizedImagePartial.create(from: $0, using: localizedStrings) + } } - var url: URL { - self.imageInfo.light.heic + @ViewBuilder + func styles( + state: ComponentViewState, + condition: ScreenCondition, + apply: @escaping (ImageComponentStyle) -> some View + ) -> some View { + let localizedPartial = LocalizedImagePartial.buildPartial( + state: state, + condition: condition, + with: self.presentedOverrides + ) + let partial = localizedPartial?.partial + + let style = ImageComponentStyle( + visible: partial?.visible ?? true, + source: localizedPartial?.imageInfo ?? self.imageInfo, + size: partial?.size ?? self.component.size, + fitMode: partial?.fitMode ?? self.component.fitMode, + maskShape: partial?.maskShape ?? self.component.maskShape, + gradientColors: partial?.gradientColors ?? self.component.gradientColors + ) + + apply(style) } - var size: PaywallComponent.Size { - self.component.size +} + +struct LocalizedImagePartial: PresentedPartial { + + let imageInfo: PaywallComponent.ThemeImageUrls? + let partial: PaywallComponent.PartialImageComponent + + static func combine(_ base: Self?, with other: Self?) -> Self { + let otherPartial = other?.partial + let basePartial = base?.partial + + return Self( + imageInfo: other?.imageInfo ?? base?.imageInfo, + partial: PaywallComponent.PartialImageComponent( + visible: otherPartial?.visible ?? basePartial?.visible, + source: otherPartial?.source ?? basePartial?.source, + size: otherPartial?.size ?? basePartial?.size, + overrideSourceLid: otherPartial?.overrideSourceLid ?? basePartial?.overrideSourceLid, + fitMode: otherPartial?.fitMode ?? basePartial?.fitMode, + maskShape: otherPartial?.maskShape ?? basePartial?.maskShape, + gradientColors: otherPartial?.gradientColors ?? basePartial?.gradientColors + ) + ) } - var shape: ShapeModifier.Shape? { - guard let shape = self.component.maskShape else { - return nil - } +} - switch shape { +extension LocalizedImagePartial { + + static func create( + from partial: PaywallComponent.PartialImageComponent, + using localizedStrings: PaywallComponent.LocalizationDictionary + ) throws -> LocalizedImagePartial { + return LocalizedImagePartial( + imageInfo: try partial.overrideSourceLid.flatMap { key in + try localizedStrings.image(key: key) + } ?? partial.source, + partial: partial + ) + } + +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct ImageComponentStyle { + + let visible: Bool + let url: URL + let size: PaywallComponent.Size + let shape: ShapeModifier.Shape? + let gradientColors: [Color] + let contentMode: ContentMode + + init( + visible: Bool = true, + source: PaywallComponent.ThemeImageUrls, + size: PaywallComponent.Size, + fitMode: PaywallComponent.FitMode, + maskShape: PaywallComponent.MaskShape? = nil, + gradientColors: [PaywallComponent.ColorHex]? = nil + ) { + self.visible = visible + self.url = source.light.heic // WIP: Do dark mode + self.size = size + self.shape = maskShape?.shape + self.gradientColors = gradientColors?.compactMap { $0.toColor(fallback: Color.clear) } ?? [] + self.contentMode = fitMode.contentMode + } + +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private extension PaywallComponent.MaskShape { + + var shape: ShapeModifier.Shape? { + switch self { case .rectangle(let cornerRadiuses): let corners = cornerRadiuses.flatMap { cornerRadiuses in ShapeModifier.RadiusInfo( @@ -69,14 +161,6 @@ class ImageComponentViewModel { } } - var gradientColors: [Color] { - component.gradientColors?.compactMap { $0.toColor(fallback: Color.clear) } ?? [] - } - - var contentMode: ContentMode { - component.fitMode.contentMode - } - } #endif diff --git a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift index 5978b9c925..406da48010 100644 --- a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift @@ -42,16 +42,16 @@ class TextComponentViewModel { condition: ScreenCondition, apply: @escaping (TextComponentStyle) -> some View ) -> some View { - let localalizedPartial = LocalizedTextPartial.buildPartial( + let localizedPartial = LocalizedTextPartial.buildPartial( state: state, condition: condition, with: self.presentedOverrides ) - let partial = localalizedPartial?.partial + let partial = localizedPartial?.partial let style = TextComponentStyle( visible: partial?.visible ?? true, - text: localalizedPartial?.text ?? self.text, + text: localizedPartial?.text ?? self.text, fontFamily: partial?.fontName ?? self.component.fontName, fontWeight: partial?.fontWeight ?? self.component.fontWeight, color: partial?.color ?? self.component.color, @@ -128,7 +128,7 @@ struct TextComponentStyle { init( visible: Bool, - text: PaywallComponent.LocalizationKey, + text: String, fontFamily: String?, fontWeight: PaywallComponent.FontWeight, color: PaywallComponent.ColorScheme,