Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Paywall Components] Introduce new LocalizationProvider to be used by the component view models #4262

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
2C0B98CD2797070B00C5874F /* PromotionalOffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0B98CC2797070B00C5874F /* PromotionalOffer.swift */; };
2C4C36132C6FBA8B00AE959B /* CompatibilityTopBarTrailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4C36122C6FBA8B00AE959B /* CompatibilityTopBarTrailing.swift */; };
2C6CC1162B8D2B6900432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6CC1152B8D2B6800432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift */; };
2C7EBD2D2C906A7B00EC388E /* LocalizationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7EBD2C2C906A7B00EC388E /* LocalizationProvider.swift */; };
2C7F0AD32B8EEB4600381179 /* RateLimiter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F0AD22B8EEB4600381179 /* RateLimiter.swift */; };
2C7F0AD62B8EEF7B00381179 /* RateLimiterRests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */; };
2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */; };
Expand Down Expand Up @@ -838,7 +839,6 @@
88DE93E12C211CBC0086B6D8 /* RevenueCat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DC5621624EC63420031F69B /* RevenueCat.framework */; };
88E679472C7503C1007E69D5 /* PaywallStackComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */; };
88EA80ED2C8771A7003E6675 /* TemplateComponentsView+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */; };
88EA80EF2C87D68F003E6675 /* PaywallComponentLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */; };
9A65DFDE258AD60A00DE00B0 /* LogIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */; };
9A65E03625918B0500DE00B0 /* ConfigureStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65E03525918B0500DE00B0 /* ConfigureStrings.swift */; };
9A65E03B25918B0900DE00B0 /* CustomerInfoStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65E03A25918B0900DE00B0 /* CustomerInfoStrings.swift */; };
Expand Down Expand Up @@ -1147,6 +1147,7 @@
2C5F71F62C3D6C2600B0FE4B /* background.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = background.heic; sourceTree = "<group>"; };
2C646C282A0EBD0300E5936E /* CI-Snapshots.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "CI-Snapshots.xctestplan"; path = "Tests/TestPlans/CI-Snapshots.xctestplan"; sourceTree = "<group>"; };
2C6CC1152B8D2B6800432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchasesSyncAttributesAndOfferingsIfNeededTests.swift; sourceTree = "<group>"; };
2C7EBD2C2C906A7B00EC388E /* LocalizationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationProvider.swift; sourceTree = "<group>"; };
2C7F0AD22B8EEB4600381179 /* RateLimiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiter.swift; sourceTree = "<group>"; };
2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiterRests.swift; sourceTree = "<group>"; };
2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformInfo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1998,7 +1999,6 @@
88B1BAE92C813A3C001B7EE5 /* StackComponentViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackComponentViewModel.swift; sourceTree = "<group>"; };
88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallStackComponent.swift; sourceTree = "<group>"; };
88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateComponentsView+extensions.swift"; sourceTree = "<group>"; };
88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallComponentLocalization.swift; sourceTree = "<group>"; };
9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogIntent.swift; sourceTree = "<group>"; };
9A65E03525918B0500DE00B0 /* ConfigureStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureStrings.swift; sourceTree = "<group>"; };
9A65E03A25918B0900DE00B0 /* CustomerInfoStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerInfoStrings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4245,7 +4245,6 @@
isa = PBXGroup;
children = (
88AD01012C740CF400AA1F2B /* PaywallComponentBase.swift */,
88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */,
88B1BAD72C812D72001B7EE5 /* PaywallComponentPropertyTypes.swift */,
88AD01032C740CF400AA1F2B /* PaywallImageComponent.swift */,
88AD01062C740CF400AA1F2B /* PaywallSpacerComponent.swift */,
Expand All @@ -4268,6 +4267,7 @@
88B1BAE32C813A3C001B7EE5 /* TemplateComponentsView.swift */,
88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */,
88B1BADC2C813A3C001B7EE5 /* Text */,
2C7EBD2C2C906A7B00EC388E /* LocalizationProvider.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -5296,7 +5296,6 @@
2C0B98CD2797070B00C5874F /* PromotionalOffer.swift in Sources */,
57DC9F4627CC2E4900DA6AF9 /* HTTPRequest.swift in Sources */,
88B1BAD82C812D72001B7EE5 /* PaywallComponentPropertyTypes.swift in Sources */,
88EA80EF2C87D68F003E6675 /* PaywallComponentLocalization.swift in Sources */,
2DC5623024EC63730031F69B /* OperationDispatcher.swift in Sources */,
35D159CB2BC4396F004D8061 /* DiagnosticsPostOperation.swift in Sources */,
575642B62910116900719219 /* EligibilityStrings.swift in Sources */,
Expand Down Expand Up @@ -5936,6 +5935,7 @@
887A60C72C1D037000E1A461 /* PackageButtonStyle.swift in Sources */,
77372D992C6F8C7B008E59D3 /* AppUpdateWarningView.swift in Sources */,
887A60C52C1D037000E1A461 /* IntroEligibilityStateView.swift in Sources */,
2C7EBD2D2C906A7B00EC388E /* LocalizationProvider.swift in Sources */,
88A543E72C37A4C40039C6A5 /* TierSelectorView.swift in Sources */,
88A543E12C37A4820039C6A5 /* TemplateView+MultiTier.swift in Sources */,
887A60B72C1D037000E1A461 /* WatchTemplateView.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions RevenueCatUI/Helpers/ImageLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class ImageLoader: ObservableObject {

}

typealias Value = Result<Image, Error>
typealias Value = Result<(image: Image, size: CGSize), Error>

@Published
private(set) var result: Value? {
Expand Down Expand Up @@ -101,13 +101,13 @@ private extension Data {
func toImage() -> ImageLoader.Value {
#if os(macOS)
if let image = NSImage(data: self) {
return .success(.init(nsImage: image))
return .success((.init(nsImage: image), image.size))
} else {
return .failure(.invalidImage)
}
#else
if let image = UIImage(data: self) {
return .success(.init(uiImage: image))
return .success((.init(uiImage: image), image.size))
} else {
return .failure(.invalidImage)
}
Expand Down
30 changes: 17 additions & 13 deletions RevenueCatUI/Modifiers/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,28 @@ extension View {
corners: UIRectCorner,
edgesIgnoringSafeArea edges: Edge.Set = []
) -> some View {
#if swift(>=5.9)
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) {
self.mask(
UnevenRoundedRectangle(radius: radius, corners: corners),
edgesIgnoringSafeArea: edges
)
} else {
if radius > 0 {
#if swift(>=5.9)
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) {
self.mask(
UnevenRoundedRectangle(radius: radius, corners: corners),
edgesIgnoringSafeArea: edges
)
} else {
self.mask(
RoundedCorner(radius: radius, corners: corners),
edgesIgnoringSafeArea: edges
)
}
#else
self.mask(
RoundedCorner(radius: radius, corners: corners),
edgesIgnoringSafeArea: edges
)
#endif
} else {
self
}
#else
self.mask(
RoundedCorner(radius: radius, corners: corners),
edgesIgnoringSafeArea: edges
)
#endif
}

private func mask(_ shape: some Shape, edgesIgnoringSafeArea edges: Edge.Set) -> some View {
Expand Down
48 changes: 35 additions & 13 deletions RevenueCatUI/Templates/Components/Image/ImageComponentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,45 @@ struct ImageComponentView: View {
let viewModel: ImageComponentViewModel

var body: some View {
RemoteImage(url: viewModel.url) { image in
image
.resizable()
.aspectRatio(contentMode: viewModel.contentMode)
.frame(maxHeight: viewModel.maxHeight)
.overlay(
LinearGradient(
gradient: Gradient(colors: viewModel.gradientColors),
startPoint: .top,
endPoint: .bottom
)
)
.cornerRadius(viewModel.cornerRadius)
RemoteImage(url: viewModel.highResUrl, lowResUrl: viewModel.lowResUrl) { (image, size) in
Group {
switch viewModel.contentMode {
case .fit:
renderImage(image, size)
case .fill:
// Need this to be in a clear color overlay so the image
// doesn't push/adjust any parent sizes
Color.clear.overlay {
renderImage(image, size)
}
}
}
// Works as a max height for both fit and fill
// using the CGSize of an image
.applyIfLet(viewModel.maxHeight, apply: { view, value in
view.frame(height: value)
})
}
.clipped()
}

private func renderImage(_ image: Image, _ size: CGSize) -> some View {
image
.resizable()
.aspectRatio(contentMode: viewModel.contentMode)
.overlay(
LinearGradient(
gradient: Gradient(colors: viewModel.gradientColors),
startPoint: .top,
endPoint: .bottom
)
)
.roundedCorner(viewModel.cornerRadiuses.topLeading, corners: .topLeft)
.roundedCorner(viewModel.cornerRadiuses.topTrailing, corners: .topRight)
.roundedCorner(viewModel.cornerRadiuses.bottomLeading, corners: .bottomLeft)
.roundedCorner(viewModel.cornerRadiuses.bottomTrailing, corners: .bottomRight)
}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,31 @@ import SwiftUI
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public class ImageComponentViewModel {

let localizationProvider: LocalizationProvider
private let component: PaywallComponent.ImageComponent

init(component: PaywallComponent.ImageComponent) {
let imageInfo: PaywallComponent.ThemeImageUrls

init(localizationProvider: LocalizationProvider,
component: PaywallComponent.ImageComponent) throws {
self.localizationProvider = localizationProvider
self.component = component
self.imageInfo = try localizationProvider.image(key: component.urlsLid)
}

public var highResUrl: URL {
self.imageInfo.light.heic
}

public var url: URL {
component.url
public var lowResUrl: URL {
self.imageInfo.light.heicLowRes
}
public var cornerRadius: Double {
component.cornerRadius

public var cornerRadiuses: PaywallComponent.CornerRadiuses {
component.cornerRadiuses
}
public var gradientColors: [Color] {
component.gradientColors.compactMap { $0.toColor(fallback: Color.clear) }
component.gradientColors?.compactMap { $0.toColor(fallback: Color.clear) } ?? []
}
public var contentMode: ContentMode {
component.fitMode.contentMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ public class LinkButtonComponentViewModel {
}

init(component: PaywallComponent.LinkButtonComponent,
localizedStrings: PaywallComponent.LocalizationDictionary
localizationProvider: LocalizationProvider
) throws {
self.component = component
self.textComponentViewModel = try TextComponentViewModel(localizedStrings: localizedStrings,
component: component.textComponent)
self.textComponentViewModel = try TextComponentViewModel(
localizationProvider: localizationProvider,
component: component.textComponent
)
}

}
Expand Down
Loading