Skip to content

Commit

Permalink
[Paywalls V2] Update Image to handle property overrides (#4477)
Browse files Browse the repository at this point in the history
* Allowed overriding image component

* Lint

* Fix typo
  • Loading branch information
joshdholtz authored Nov 13, 2024
1 parent 8b77154 commit 02df65f
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 29 deletions.
27 changes: 19 additions & 8 deletions RevenueCatUI/Templates/Components/Image/ImageComponentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}
Expand Down
118 changes: 101 additions & 17 deletions RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ImageComponentViewModel {
private let component: PaywallComponent.ImageComponent

private let imageInfo: PaywallComponent.ThemeImageUrls
private let presentedOverrides: PresentedOverrides<LocalizedImagePartial>?

init(localizedStrings: PaywallComponent.LocalizationDictionary, component: PaywallComponent.ImageComponent) throws {
self.localizedStrings = localizedStrings
Expand All @@ -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(
Expand All @@ -69,14 +161,6 @@ class ImageComponentViewModel {
}
}

var gradientColors: [Color] {
component.gradientColors?.compactMap { $0.toColor(fallback: Color.clear) } ?? []
}

var contentMode: ContentMode {
component.fitMode.contentMode
}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -128,7 +128,7 @@ struct TextComponentStyle {

init(
visible: Bool,
text: PaywallComponent.LocalizationKey,
text: String,
fontFamily: String?,
fontWeight: PaywallComponent.FontWeight,
color: PaywallComponent.ColorScheme,
Expand Down

0 comments on commit 02df65f

Please sign in to comment.