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

Paywalls: added header image to configuration #2800

Merged
merged 1 commit into from
Jul 13, 2023
Merged
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
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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 @@ -204,6 +204,8 @@
4F0BBAAC2A1D253D000E75AB /* OfflineCustomerInfoCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0BBAAB2A1D253D000E75AB /* OfflineCustomerInfoCreatorTests.swift */; };
4F0CE2BD2A215CE600561895 /* TransactionPosterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CE2BC2A215CE600561895 /* TransactionPosterTests.swift */; };
4F1428A42A4A132C006CD196 /* TestStoreProductDiscount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1428A32A4A132C006CD196 /* TestStoreProductDiscount.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 @@ -3621,6 +3623,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 @@ -3663,6 +3666,7 @@
57488AFF29CA58050000EE7E /* LoadShedderIntegrationTests.swift in Sources */,
575A8EE22922C56300936709 /* AsyncTestHelpers.swift in Sources */,
2DA85A8B26DEA7DD00F1136D /* MockProductsRequestFactory.swift in Sources */,
4F1E84022A6062C9000AF177 /* ImageSnapshot.swift in Sources */,
2D3BFAD326DEA47100370B11 /* MockSKProductDiscount.swift in Sources */,
4F83F6B72A5DB782003F90A5 /* CurrentTestCaseTracker.swift in Sources */,
57DE80BE28077010008D6C6F /* XCTestCase+Extensions.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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine for now, but I think sooner or later we'll have to move into a system that pre-downloads and caches the image. Paywalls need to have the smallest possible wait time to load

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup 👍🏻

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @guido732 @lburdock FYI we're hardcoding the aspect ratio here for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah once we figure out what we want to do for the "actual" template we can revisit this.


}

// 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