Skip to content

Commit

Permalink
Merge pull request #162 from superwall-me/develop
Browse files Browse the repository at this point in the history
v3.3.1
  • Loading branch information
yusuftor committed Aug 18, 2023
2 parents 503d64e + 7ed0ef2 commit e673a54
Show file tree
Hide file tree
Showing 73 changed files with 880 additions and 291 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,19 @@

The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/Superwall-iOS/releases) on GitHub.

## 3.3.1

### Enhancements

- Adds logic to enhance debugging by sending a stringified version of all the device/user/event parameters used to evaluate rules within the `paywallPresentationRequest` event. This is behind a feature flag.
- Adds logic to keep the user's generated `seed` value consistent when `Superwall.identify` is called. This is behind a feature flag.

### Fixes

- Fixes rare issue when using limits on a campaign rule. If a paywall encountered an error preventing it from being presented, it may still have been counted as having been presented. This would then have affected future paywall presentation requests underneath the same rule.
- Fixes issue where assets weren't being accessed correctly when installing the SDK via CocoaPods.
- Fixes crash if you tried to save an object that didn't conform to NSSecureCoding in user attributes.

## 3.3.0

### Enhancements
Expand Down
76 changes: 31 additions & 45 deletions Examples/UIKit-Swift/Superwall-UIKit-Swift/HomeViewController.swift
Expand Up @@ -58,44 +58,39 @@ final class HomeViewController: UIViewController {
}

@IBAction private func launchFeature() {
Task {
let paywall = try? await Superwall.shared.getPaywall(forEvent: "campaign_trigger", delegate: self)
self.present(paywall!, animated: true)
let handler = PaywallPresentationHandler()
handler.onDismiss { paywallInfo in
print("The paywall dismissed. PaywallInfo:", paywallInfo)
}
handler.onPresent { paywallInfo in
print("The paywall presented. PaywallInfo:", paywallInfo)
}
handler.onError { error in
print("The paywall presentation failed with error \(error)")
}
handler.onSkip { reason in
switch reason {
case .userIsSubscribed:
print("Paywall not shown because user is subscribed.")
case .holdout(let experiment):
print("Paywall not shown because user is in a holdout group in Experiment: \(experiment.id)")
case .noRuleMatch:
print("Paywall not shown because user doesn't match any rules.")
case .eventNotFound:
print("Paywall not shown because this event isn't part of a campaign.")
}
}

// let handler = PaywallPresentationHandler()
// handler.onDismiss { paywallInfo in
// print("The paywall dismissed. PaywallInfo:", paywallInfo)
// }
// handler.onPresent { paywallInfo in
// print("The paywall presented. PaywallInfo:", paywallInfo)
// }
// handler.onError { error in
// print("The paywall presentation failed with error \(error)")
// }
// handler.onSkip { reason in
// switch reason {
// case .userIsSubscribed:
// print("Paywall not shown because user is subscribed.")
// case .holdout(let experiment):
// print("Paywall not shown because user is in a holdout group in Experiment: \(experiment.id)")
// case .noRuleMatch:
// print("Paywall not shown because user doesn't match any rules.")
// case .eventNotFound:
// print("Paywall not shown because this event isn't part of a campaign.")
// }
// }
//
// Superwall.shared.register(event: "campaign_trigger", handler: handler) {
// // code in here can be remotely configured to execute. Either
// // (1) always after presentation or
// // (2) only if the user pays
// // code is always executed if no paywall is configured to show
// self.presentAlert(
// title: "Feature Launched",
// message: "Wrap your awesome features in register calls like this to remotely paywall your app. You can remotely decide whether these are paid features."
// )
// }
Superwall.shared.register(event: "campaign_trigger", handler: handler) {
// code in here can be remotely configured to execute. Either
// (1) always after presentation or
// (2) only if the user pays
// code is always executed if no paywall is configured to show
self.presentAlert(
title: "Feature Launched",
message: "Wrap your awesome features in register calls like this to remotely paywall your app. You can remotely decide whether these are paid features."
)
}
}

private func presentAlert(title: String, message: String) {
Expand All @@ -110,12 +105,3 @@ final class HomeViewController: UIViewController {
self.present(alertController, animated: true)
}
}

extension HomeViewController: PaywallViewControllerDelegate {
func paywall(_ paywall: PaywallViewController, didFinishWith result: PaywallResult, shouldDismiss: Bool) {
print(result)
if shouldDismiss {
paywall.dismiss(animated: true)
}
}
}
Expand Up @@ -288,18 +288,41 @@ enum InternalSuperwallEvent {
let type: PresentationRequestType
let status: PaywallPresentationRequestStatus
let statusReason: PaywallPresentationRequestStatusReason?
let factory: RuleAttributesFactory & FeatureFlagsFactory & ComputedPropertyRequestsFactory

var superwallEvent: SuperwallEvent {
return .paywallPresentationRequest(status: status, reason: statusReason)
return .paywallPresentationRequest(
status: status,
reason: statusReason
)
}
var customParameters: [String: Any] = [:]
func getSuperwallParameters() async -> [String: Any] {
[
var params = [
"source_event_name": eventData?.name ?? "",
"pipeline_type": type.description,
"status": status.rawValue,
"status_reason": statusReason?.description ?? ""
]

if let featureFlags = factory.makeFeatureFlags(),
featureFlags.enableExpressionParameters {
let computedPropertyRequests = factory.makeComputedPropertyRequests()
let rules = await factory.makeRuleAttributes(
forEvent: eventData,
withComputedProperties: computedPropertyRequests
)

if let rulesDictionary = rules.dictionaryObject,
let jsonData = try? JSONSerialization.data(withJSONObject: rulesDictionary),
let decoded = String(data: jsonData, encoding: .utf8) {
params += [
"expression_params": decoded
]
}
}

return params
}
}

Expand All @@ -321,8 +344,20 @@ enum InternalSuperwallEvent {
return .paywallClose(paywallInfo: paywallInfo)
}
let paywallInfo: PaywallInfo
let surveyPresentationResult: SurveyPresentationResult

func getSuperwallParameters() async -> [String: Any] {
return await paywallInfo.eventParams()
var params: [String: Any] = [
"survey_attached": paywallInfo.survey == nil ? false : true
]

if surveyPresentationResult != .noShow {
params["survey_presentation"] = surveyPresentationResult.rawValue
}

let eventParams = await paywallInfo.eventParams()
params += eventParams
return params
}
var customParameters: [String: Any] {
return paywallInfo.customParams()
Expand Down
Expand Up @@ -54,7 +54,7 @@ public enum SuperwallEvent {
/// When a paywall is closed.
case paywallClose(paywallInfo: PaywallInfo)

/// When a user dismisses a paywall instead of purchasing
/// When a user manually dismisses a paywall.
case paywallDecline(paywallInfo: PaywallInfo)

/// When the payment sheet is displayed to the user.
Expand Down
7 changes: 7 additions & 0 deletions Sources/SuperwallKit/Config/Models/Config.swift
Expand Up @@ -17,6 +17,13 @@ struct Config: Decodable {
var featureFlags: FeatureFlags
var preloadingDisabled: PreloadingDisabled
var requestId: String?
var allComputedProperties: [ComputedPropertyRequest] {
return triggers.flatMap {
$0.rules.flatMap {
$0.computedPropertyRequests
}
}
}

enum CodingKeys: String, CodingKey {
case triggers = "triggerOptions"
Expand Down
14 changes: 12 additions & 2 deletions Sources/SuperwallKit/Config/Models/FeatureFlags.swift
Expand Up @@ -15,6 +15,8 @@ struct RawFeatureFlag: Decodable {
struct FeatureFlags: Decodable {
var enableSessionEvents: Bool
var enablePostback: Bool
var enableExpressionParameters: Bool
var enableUserIdSeed: Bool

enum CodingKeys: String, CodingKey {
case toggles
Expand All @@ -25,15 +27,21 @@ struct FeatureFlags: Decodable {
let rawFeatureFlags = try values.decode([RawFeatureFlag].self, forKey: .toggles)

enableSessionEvents = rawFeatureFlags.value(forKey: "enable_session_events", default: false)
enableExpressionParameters = rawFeatureFlags.value(forKey: "enable_expression_params", default: false)
enablePostback = rawFeatureFlags.value(forKey: "enable_postback", default: false)
enableUserIdSeed = rawFeatureFlags.value(forKey: "enable_userid_seed", default: false)
}

init(
enableSessionEvents: Bool,
enablePostback: Bool
enablePostback: Bool,
enableExpressionParameters: Bool,
enableUserIdSeed: Bool
) {
self.enableSessionEvents = enableSessionEvents
self.enablePostback = enablePostback
self.enableExpressionParameters = enableExpressionParameters
self.enableUserIdSeed = enableUserIdSeed
}
}

Expand All @@ -53,7 +61,9 @@ extension FeatureFlags: Stubbable {
static func stub() -> FeatureFlags {
return FeatureFlags(
enableSessionEvents: true,
enablePostback: true
enablePostback: true,
enableExpressionParameters: true,
enableUserIdSeed: true
)
}
}
23 changes: 11 additions & 12 deletions Sources/SuperwallKit/Config/Models/Survey.swift
Expand Up @@ -34,31 +34,30 @@ final public class Survey: NSObject, Decodable {
/// response.
public let includeOtherOption: Bool

/// Rolls dice to see if survey should present.
func shouldPresent(
/// Rolls dice to see if survey should present or is in holdout.
///
/// - Returns: `true` if user is in holdout, false if survey should present.
func shouldAssignHoldout(
isDebuggerLaunched: Bool,
storage: Storage
storage: Storage,
randomiser: (Range<Double>) -> Double = Double.random
) -> Bool {
if isDebuggerLaunched {
return true
return false
}
// Return immediately if no chance to present.
if presentationProbability == 0 {
return false
return true
}

// Choose random number to present the survey with
// the probability of presentationProbability.
let randomNumber = Double.random(in: 0..<1)
let randomNumber = randomiser(0..<1)
guard randomNumber < presentationProbability else {
return false
}

if hasSeenSurvey(storage: storage) {
return false
return true
}

return true
return false
}

/// Determines whether a survey with the same `assignmentKey` has been
Expand Down
16 changes: 8 additions & 8 deletions Sources/SuperwallKit/Debug/DebugViewController.swift
Expand Up @@ -26,7 +26,7 @@ struct AlertOption {
@MainActor
final class DebugViewController: UIViewController {
var logoImageView: UIImageView = {
let superwallLogo = UIImage(named: "superwall_logo", in: Bundle.module, compatibleWith: nil)!
let superwallLogo = UIImage(named: "SuperwallKit_superwall_logo", in: Bundle.module, compatibleWith: nil)!
let imageView = UIImageView(image: superwallLogo)
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .clear
Expand All @@ -38,7 +38,7 @@ final class DebugViewController: UIViewController {

lazy var exitButton: SWBounceButton = {
let button = SWBounceButton()
let image = UIImage(named: "exit", in: Bundle.module, compatibleWith: nil)!
let image = UIImage(named: "SuperwallKit_exit", in: Bundle.module, compatibleWith: nil)!
button.setImage(image, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.imageView?.tintColor = UIColor.white.withAlphaComponent(0.5)
Expand All @@ -48,7 +48,7 @@ final class DebugViewController: UIViewController {

lazy var consoleButton: SWBounceButton = {
let button = SWBounceButton()
let image = UIImage(named: "debugger", in: Bundle.module, compatibleWith: nil)!
let image = UIImage(named: "SuperwallKit_debugger", in: Bundle.module, compatibleWith: nil)!
button.setImage(image, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.imageView?.tintColor = UIColor.white.withAlphaComponent(0.5)
Expand All @@ -64,7 +64,7 @@ final class DebugViewController: UIViewController {
button.setTitleColor(primaryColor, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false

let image = UIImage(named: "play_button", in: Bundle.module, compatibleWith: nil)!
let image = UIImage(named: "SuperwallKit_play_button", in: Bundle.module, compatibleWith: nil)!
button.titleEdgeInsets = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0)
// button.imageEdgeInsets = UIEdgeInsets(top: 1, left: 5, bottom: -1, right: -3)
button.setImage(image, for: .normal)
Expand All @@ -86,7 +86,7 @@ final class DebugViewController: UIViewController {
button.imageView?.tintColor = primaryColor
button.layer.cornerRadius = 10

let image = UIImage(named: "down_arrow", in: Bundle.module, compatibleWith: nil)!
let image = UIImage(named: "SuperwallKit_down_arrow", in: Bundle.module, compatibleWith: nil)!
button.semanticContentAttribute = .forceRightToLeft
button.setImage(image, for: .normal)
button.imageView?.tintColor = primaryColor
Expand Down Expand Up @@ -448,7 +448,7 @@ final class DebugViewController: UIViewController {
case .presented:
self.bottomButton.showLoading = false

let playButton = UIImage(named: "play_button", in: Bundle.module, compatibleWith: nil)!
let playButton = UIImage(named: "SuperwallKit_play_button", in: Bundle.module, compatibleWith: nil)!
self.bottomButton.setImage(
playButton,
for: .normal
Expand All @@ -473,7 +473,7 @@ final class DebugViewController: UIViewController {
)
self.bottomButton.showLoading = false

let playButton = UIImage(named: "play_button", in: Bundle.module, compatibleWith: nil)!
let playButton = UIImage(named: "SuperwallKit_play_button", in: Bundle.module, compatibleWith: nil)!
self.bottomButton.setImage(playButton, for: .normal)
self.activityIndicator.stopAnimating()
case .dismissed:
Expand All @@ -492,7 +492,7 @@ final class DebugViewController: UIViewController {
)
self.bottomButton.showLoading = false

let playButton = UIImage(named: "play_button", in: Bundle.module, compatibleWith: nil)!
let playButton = UIImage(named: "SuperwallKit_play_button", in: Bundle.module, compatibleWith: nil)!
self.bottomButton.setImage(playButton, for: .normal)
self.activityIndicator.stopAnimating()
}
Expand Down

0 comments on commit e673a54

Please sign in to comment.