Skip to content

Commit

Permalink
Paywalls: added header image to configuration (#2800)
Browse files Browse the repository at this point in the history
- Added `PaywallData.assetBaseURL` and
`PaywallData.Configuration.headerImageName`
- Changed template to fetch image from network
- Updated snapshot testing to support asynchronous checking, necessary
for `AsyncImageView`
- Added support for overriding images for snapshot tests
- Extracted `DebugErrorView`
  • Loading branch information
NachoSoto committed Sep 6, 2023
1 parent cbf6b56 commit bf71336
Show file tree
Hide file tree
Showing 21 changed files with 316 additions and 86 deletions.
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ let package = Package(
"RevenueCatUI",
"Nimble",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
])
],
resources: [.copy("Resources/image.png")])
]
)
4 changes: 4 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@
4F15B4A12A6774C9005BEFE8 /* CustomerInfo+NonSubscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F15B4A02A6774C9005BEFE8 /* CustomerInfo+NonSubscriptions.swift */; };
4F15B4A22A678A9C005BEFE8 /* MockStoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */; };
4F15B4A32A678B81005BEFE8 /* MockStoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */; };
4F1E84012A6062C1000AF177 /* ImageSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCEEA622A37A2E9002C2112 /* ImageSnapshot.swift */; };
4F1E84022A6062C9000AF177 /* ImageSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCEEA622A37A2E9002C2112 /* ImageSnapshot.swift */; };
4F2017D52A15587F0061F6EF /* OfflineStoreKitIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2017D42A15587F0061F6EF /* OfflineStoreKitIntegrationTests.swift */; };
4F2018732A15797D0061F6EF /* TestLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57057FF728B0048900995F21 /* TestLogHandler.swift */; };
4F2F2EFF2A3CDAA800652B24 /* FileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2F2EFE2A3CDAA800652B24 /* FileHandler.swift */; };
Expand Down Expand Up @@ -3672,6 +3674,7 @@
5733B1A427FF9F8300EC2045 /* NetworkErrorTests.swift in Sources */,
351B517026D44E8D00BD2BD7 /* MockDateProvider.swift in Sources */,
4F2F2F142A3CEAB500652B24 /* FileHandlerTests.swift in Sources */,
4F1E84012A6062C1000AF177 /* ImageSnapshot.swift in Sources */,
57FDAAC028493C13009A48F1 /* MockSandboxEnvironmentDetector.swift in Sources */,
5766AAD1283E981700FA6091 /* PurchasesPurchasingTests.swift in Sources */,
351B515E26D44B9900BD2BD7 /* MockPurchasesDelegate.swift in Sources */,
Expand Down Expand Up @@ -3716,6 +3719,7 @@
4FDF10F02A7262D8004F3680 /* SK2ProductFetcher.swift in Sources */,
575A8EE22922C56300936709 /* AsyncTestHelpers.swift in Sources */,
2DA85A8B26DEA7DD00F1136D /* MockProductsRequestFactory.swift in Sources */,
4F1E84022A6062C9000AF177 /* ImageSnapshot.swift in Sources */,
2D3BFAD326DEA47100370B11 /* MockSKProductDiscount.swift in Sources */,
4FDF10ED2A726291004F3680 /* SK1ProductFetcher.swift in Sources */,
4F83F6B72A5DB782003F90A5 /* CurrentTestCaseTracker.swift in Sources */,
Expand Down
42 changes: 42 additions & 0 deletions RevenueCatUI/Helpers/DebugErrorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// DebugErrorView.swift
//
//
// Created by Nacho Soto on 7/13/23.
//

import Foundation
import SwiftUI

/// A view that displays an error in debug builds
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
struct DebugErrorView: View {

private let description: String

init(_ error: Error) {
self.init((error as NSError).localizedDescription)
}

init(_ description: String) {
self.description = description
}

var body: some View {
#if DEBUG
Text(self.description)
.background(
Color.red
.edgesIgnoringSafeArea(.all)
)
#else
// Fix-me: implement a proper production error screen
// appropriate for each case
EmptyView()
.onAppear {
Logger.warning("Couldn't load paywall: \(self.description)")
}
#endif
}

}
36 changes: 36 additions & 0 deletions RevenueCatUI/Modifiers/FitToAspectRatio.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// FitToAspectRatio.swift
//
//
// Created by Nacho Soto on 7/13/23.
//

import Foundation
import SwiftUI

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
struct FitToAspectRatio: ViewModifier {

let aspectRatio: Double
let contentMode: SwiftUI.ContentMode

func body(content: Content) -> some View {
Color.clear
.aspectRatio(self.aspectRatio, contentMode: .fit)
.overlay(
content.aspectRatio(nil, contentMode: self.contentMode)
)
}

}

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
extension Image {

func fitToAspect(_ aspectRatio: Double, contentMode: SwiftUI.ContentMode) -> some View {
self.resizable()
.scaledToFill()
.modifier(FitToAspectRatio(aspectRatio: aspectRatio, contentMode: contentMode))
}

}
6 changes: 0 additions & 6 deletions RevenueCatUI/Resources/Assets.xcassets/Contents.json

This file was deleted.

This file was deleted.

65 changes: 36 additions & 29 deletions RevenueCatUI/Templates/Example1Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ struct Example1Template: TemplateViewType {
init(
packages: [Package],
localization: PaywallData.LocalizedConfiguration,
configuration: PaywallData.Configuration
paywall: PaywallData
) {
// Fix-me: move this logic out to be used by all templates
if packages.isEmpty {
self.data = .failure(.noPackages)
} else {
let packages = Self.filter(packages: packages, with: configuration.packages)
let allPackages = paywall.config.packages
let packages = Self.filter(packages: packages, with: allPackages)

if let package = packages.first {
self.data = .success(.init(
package: package,
localization: localization.processVariables(with: package),
configuration: configuration
configuration: paywall.config,
headerImageURL: paywall.headerImageURL
))
} else {
self.data = .failure(.couldNotFindAnyPackages(expectedTypes: configuration.packages))
self.data = .failure(.couldNotFindAnyPackages(expectedTypes: allPackages))
}
}
}
Expand All @@ -35,19 +37,7 @@ struct Example1Template: TemplateViewType {
case let .success(data):
Example1TemplateContent(data: data)
case let .failure(error):
#if DEBUG
// Fix-me: implement a proper production error screen
EmptyView()
.onAppear {
Logger.warning("Couldn't load paywall: \(error.description)")
}
#else
Text(error.description)
.background(
Color.red
.edgesIgnoringSafeArea(.all)
)
#endif
DebugErrorView(error)
}
}

Expand All @@ -71,17 +61,25 @@ private struct Example1TemplateContent: View {
@ViewBuilder
private var content: some View {
VStack {
Image("image", bundle: .module)
.resizable()
.frame(maxWidth: .infinity)
.aspectRatio(1, contentMode: .fill)
.edgesIgnoringSafeArea(.top)
.padding(.bottom)
.mask(alignment: .top) {
Circle()
.offset(y: -160)
.scale(2.5)
AsyncImage(url: self.data.headerImageURL) { phase in
if let image = phase.image {
image
.fitToAspect(Self.imageAspectRatio, contentMode: .fill)
.edgesIgnoringSafeArea(.top)
} else if let error = phase.error {
DebugErrorView("Error loading image from '\(self.data.headerImageURL)': \(error)")
} else {
Rectangle()
.hidden()
}
}
.frame(maxWidth: .infinity)
.aspectRatio(Self.imageAspectRatio, contentMode: .fit)
.clipShape(
Circle()
.offset(y: -100)
.scale(3.0)
)

Spacer()

Expand Down Expand Up @@ -133,6 +131,8 @@ private struct Example1TemplateContent: View {
.controlSize(.large)
}

private static let imageAspectRatio = 0.7

}

// MARK: -
Expand All @@ -144,6 +144,7 @@ private extension Example1TemplateContent {
let package: Package
let localization: ProcessedLocalizedConfiguration
let configuration: PaywallData.Configuration
let headerImageURL: URL
}

enum Error: Swift.Error {
Expand Down Expand Up @@ -173,9 +174,15 @@ private extension Example1TemplateContent {
}

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
extension Example1TemplateContent.Error {
extension Example1TemplateContent.Error: CustomNSError {

var errorUserInfo: [String: Any] {
return [
NSLocalizedDescriptionKey: self.description
]
}

var description: String {
private var description: String {
switch self {
case .noPackages:
return "Attempted to display paywall with no packages."
Expand Down
4 changes: 2 additions & 2 deletions RevenueCatUI/Templates/TemplateViewType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ protocol TemplateViewType: SwiftUI.View {
init(
packages: [Package],
localization: PaywallData.LocalizedConfiguration,
configuration: PaywallData.Configuration
paywall: PaywallData
)

}
Expand All @@ -22,7 +22,7 @@ extension PaywallData {
Example1Template(
packages: offering.availablePackages,
localization: self.localizedConfiguration,
configuration: self.config
paywall: self
)
}
}
Expand Down
14 changes: 10 additions & 4 deletions RevenueCatUI/TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,20 @@ internal enum TestData {
static let paywallWithIntroOffer = PaywallData(
template: .example1,
config: .init(
packages: [.monthly]
packages: [.monthly],
headerImageName: Self.paywallHeaderImageName
),
localization: Self.localization
localization: Self.localization,
assetBaseURL: Self.paywallAssetBaseURL
)
static let paywallWithNoIntroOffer = PaywallData(
template: .example1,
config: .init(
packages: [.annual]
packages: [.annual],
headerImageName: Self.paywallHeaderImageName
),
localization: Self.localization
localization: Self.localization,
assetBaseURL: Self.paywallAssetBaseURL
)

static let offeringWithIntroOffer = Offering(
Expand Down Expand Up @@ -103,6 +107,8 @@ internal enum TestData {
)

private static let offeringIdentifier = "offering"
private static let paywallHeaderImageName = "cd84ac55_paywl0884b9ceb4_header_1689214657.jpg"
private static let paywallAssetBaseURL = URL(string: "https://d2ban7feka8lu3.cloudfront.net")!

}

Expand Down
Loading

0 comments on commit bf71336

Please sign in to comment.