-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Paywalls
: extracted configuration processing into a new `TemplateVi…
…ewConfiguration` ### Changes: - Created `TemplateViewConfiguration` to encapsulate all processed configuration for a paywall - Created `TemplateViewConfiguration.PackageSetting` and `TemplateViewConfiguration.PackageConfiguration` to represent paywalls that support 1 or N packages - Taken all common logic out of `Example1Template` - Added new `PaywallViewMode` for upcoming "ramps" views
- Loading branch information
Showing
13 changed files
with
482 additions
and
235 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// PaywallViewMode.swift | ||
// | ||
// | ||
// Created by Nacho Soto on 7/17/23. | ||
// | ||
|
||
import RevenueCat | ||
|
||
/// The mode for how a paywall is rendered. | ||
public enum PaywallViewMode { | ||
|
||
/// Paywall is displayed full-screen, with as much information as available. | ||
case fullScreen | ||
|
||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// | ||
// TemplateError.swift | ||
// | ||
// | ||
// Created by Nacho Soto on 7/17/23. | ||
// | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
/// Error produced when processing `PaywallData`. | ||
enum TemplateError: Error { | ||
|
||
/// No packages available to create a paywall. | ||
case noPackages | ||
|
||
/// Paywall configuration contained no package types. | ||
case emptyPackageList | ||
|
||
/// No packages from the `PackageType` list could be found. | ||
case couldNotFindAnyPackages(expectedTypes: [PackageType]) | ||
|
||
} | ||
|
||
extension TemplateError: CustomNSError { | ||
|
||
var errorUserInfo: [String: Any] { | ||
return [ | ||
NSLocalizedDescriptionKey: self.description | ||
] | ||
} | ||
|
||
private var description: String { | ||
switch self { | ||
case .noPackages: | ||
return "Attempted to display paywall with no packages." | ||
|
||
case .emptyPackageList: | ||
return "Paywall configuration container no packages." | ||
|
||
case let .couldNotFindAnyPackages(expectedTypes): | ||
return "Couldn't find any requested packages: \(expectedTypes)" | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// | ||
// TemplateViewConfiguration.swift | ||
// | ||
// | ||
// Created by Nacho Soto on 7/17/23. | ||
// | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
/// The processed data necessary to render a `TemplateViewType`. | ||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
struct TemplateViewConfiguration { | ||
|
||
let mode: PaywallViewMode | ||
let packages: PackageConfiguration | ||
let configuration: PaywallData.Configuration | ||
let colors: PaywallData.Configuration.Colors | ||
let headerImageURL: URL | ||
|
||
} | ||
|
||
// MARK: - Packages | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension TemplateViewConfiguration { | ||
|
||
/// A `Package` with its processed localized strings. | ||
struct Package { | ||
|
||
let content: RevenueCat.Package | ||
let localization: ProcessedLocalizedConfiguration | ||
|
||
} | ||
|
||
/// Whether a template displays 1 or multiple packages. | ||
enum PackageSetting { | ||
|
||
case single | ||
case multiple | ||
|
||
} | ||
|
||
/// Describes the possible displayed packages in a paywall. | ||
/// See `create(with:filter:setting:)` for how to create these. | ||
enum PackageConfiguration { | ||
|
||
case single(Package) | ||
case multiple([Package]) | ||
|
||
} | ||
|
||
} | ||
|
||
// MARK: - Properties | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension TemplateViewConfiguration.PackageConfiguration { | ||
|
||
/// Returns a single package, useful for templates that expect a single package. | ||
var single: TemplateViewConfiguration.Package { | ||
switch self { | ||
case let .single(package): | ||
return package | ||
case let .multiple(packages): | ||
guard let package = packages.first else { | ||
// `create()` makes this impossible. | ||
fatalError("Unexpectedly found no packages in `PackageConfiguration.multiple`") | ||
} | ||
|
||
return package | ||
} | ||
} | ||
|
||
/// Returns all packages, useful for templates that expect multiple packages | ||
var all: [TemplateViewConfiguration.Package] { | ||
switch self { | ||
case let .single(package): | ||
return [package] | ||
case let .multiple(packages): | ||
return packages | ||
} | ||
} | ||
|
||
} | ||
|
||
// MARK: - Creation | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension TemplateViewConfiguration.PackageConfiguration { | ||
|
||
/// Creates a `PackageConfiguration` based on `setting`. | ||
/// - Throws: `TemplateError` | ||
static func create( | ||
with packages: [RevenueCat.Package], | ||
filter: [PackageType], | ||
localization: PaywallData.LocalizedConfiguration, | ||
setting: TemplateViewConfiguration.PackageSetting | ||
) throws -> Self { | ||
guard !packages.isEmpty else { throw TemplateError.noPackages } | ||
guard !filter.isEmpty else { throw TemplateError.emptyPackageList } | ||
|
||
let filtered = TemplateViewConfiguration | ||
.filter(packages: packages, with: filter) | ||
.map { package in | ||
TemplateViewConfiguration.Package( | ||
content: package, | ||
localization: localization.processVariables(with: package)) | ||
} | ||
|
||
guard let firstPackage = filtered.first else { | ||
throw TemplateError.couldNotFindAnyPackages(expectedTypes: filter) | ||
} | ||
|
||
switch setting { | ||
case .single: | ||
return .single(firstPackage) | ||
case .multiple: | ||
return .multiple(filtered) | ||
} | ||
} | ||
|
||
} | ||
|
||
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) | ||
extension TemplateViewConfiguration { | ||
|
||
/// Filters `packages`, extracting only the values corresponding to `list`. | ||
static func filter(packages: [RevenueCat.Package], with list: [PackageType]) -> [RevenueCat.Package] { | ||
// Only subscriptions are supported at the moment | ||
let subscriptions = packages.filter { $0.storeProduct.productCategory == .subscription } | ||
let map = Dictionary(grouping: subscriptions) { $0.packageType } | ||
|
||
return list.compactMap { type in | ||
if let packages = map[type] { | ||
switch packages.count { | ||
case 0: | ||
// This isn't actually possible because of `Dictionary(grouping:by:) | ||
return nil | ||
case 1: | ||
return packages.first | ||
default: | ||
Logger.warning("Found multiple \(type) packages. Will use the first one.") | ||
return packages.first | ||
} | ||
} else { | ||
Logger.warning("Couldn't find '\(type)'") | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.