Skip to content

Commit

Permalink
Merge pull request #150 from superwall-me/develop
Browse files Browse the repository at this point in the history
v3.1.0
  • Loading branch information
yusuftor authored Jul 5, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents ffc8ee1 + c670a72 commit 72b26fa
Showing 44 changed files with 931 additions and 279 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>classNames</key>
<dict>
<key>CoreDataManagerTests</key>
<dict>
<key>test_countTriggerRuleOccurrences_minutes()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.000849</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>runDestinationsByUUID</key>
<dict>
<key>A601F2E3-F1FD-45AD-AB16-B47C8B7F81DD</key>
<dict>
<key>localComputer</key>
<dict>
<key>busSpeedInMHz</key>
<integer>0</integer>
<key>cpuCount</key>
<integer>1</integer>
<key>cpuKind</key>
<string>Apple M1 Max</string>
<key>cpuSpeedInMHz</key>
<integer>0</integer>
<key>logicalCPUCoresPerPackage</key>
<integer>10</integer>
<key>modelCode</key>
<string>MacBookPro18,2</string>
<key>physicalCPUCoresPerPackage</key>
<integer>10</integer>
<key>platformIdentifier</key>
<string>com.apple.platform.macosx</string>
</dict>
<key>targetArchitecture</key>
<string>arm64</string>
<key>targetDevice</key>
<dict>
<key>modelCode</key>
<string>iPhone15,2</string>
<key>platformIdentifier</key>
<string>com.apple.platform.iphonesimulator</string>
</dict>
</dict>
</dict>
</dict>
</plist>
26 changes: 21 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,11 +2,27 @@

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

## 3.1.0

### Enhancements

- Adds support for paywalls that include a free trial notification. After starting a free trial, the app checks whether the paywall should notify the user when their trial is about to end. If so, the user will be asked to enable notifications (if they haven't already) before scheduling a local notification. You can add a free trial notification to your paywall from the paywall editor.
- Adds ability to use `device.minutesSince_X`, `device.hoursSince_X`, `device.daysSince_X`, `device.monthsSince_X`, and `device.yearsSince_X` in campaign rules and paywalls, where `X` is an event name. This can include Superwall events, such as `app_open`, or your own events.
- Prints out the Superwall SDK version when the `debug` logLevel is enabled.
- Adds `removeAllPendingSuperwallNotificationRequests()`, `removeAllPendingNonSuperwallNotificationRequests()`, `removeAllDeliveredSuperwallNotifications()`, and `removeAllDeliveredNonSuperwallNotifications()` to `UNUserNotificationCenter`. You can use these methods to remove your app's notifications without affecting Superwall's local notifications and vice-versa.
- Updates RevenueCat to the latest version in our RevenueCat example app.

### Fixes

- Fixes a Core Data multi-threading issue when performing a count. If you had enabled Core Data multi-threading assertions in Xcode, this will have caused a crash.
- Fixes very rare crash when purchasing without a `PurchaseController`.
- Reduces reliance on Combine when using register to fix memory management crashes.

## 3.0.3

### Fixes

- Fixes an issue where Superwall events such as `app_launch` weren't working as paywall triggers.
- Fixes an issue where Superwall events `app_launch`, `app_install`, and `session_start` weren't working as paywall triggers from a cold start.

## 3.0.2

@@ -192,20 +208,20 @@ We understand that transitions between major SDK releases can become frustrating
- Removes the `closedForNextPaywall` case from `PaywallResult` in favor of a new `PaywallInfo` property called `closeReason`, which can either be `nil`, `.systemLogic`, or `.forNextPaywall`.
- Changes the `PaywallPresentationHandler` variables to functions.
- Removes `Superwall.shared.track`. We're going all in on `Superwall.shared.register` baby!
- Removes .error(Error) from `PaywallSkippedReason` in favor of a new `PaywallState` case `.presentationError(Error)`.
- Removes `PaywallPresentationHandler` completion block variables removed in favor of function calls with the same names.
- Removes .error(Error) from `PaywallSkippedReason` in favor of a new `PaywallState` case `.presentationError(Error)`.
- Removes `PaywallPresentationHandler` completion block variables removed in favor of function calls with the same names.
- Changes `.onError` of `PaywallPresentationHandler` to no longer be called when a paywall is intentionally not shown (i.e. user is subscribed, user is in holdout, no rule match, event not configured)
- Adds `.onSkip(reason:)` to `PaywallPresentationHandler` to handle cases where paywall isn't shown because user is subscribed, user is in holdout, no rules match, event not configured

### Enhancements

- Adds `getPaywallViewController`! You can no request an actual view controller to present however you like. Check function documentation in Xcode for instructions – follow directions closely.
- Adds `getPaywallViewController`! You can no request an actual view controller to present however you like. Check function documentation in Xcode for instructions – follow directions closely.
- Changes default logging level to `INFO`.
- Adds new automatically tracked `paywall_decline` event that can be used to present a new paywall when a user dismisses a paywall.
- Allows `transaction_abandon` to trigger new paywalls when added to a campaign – called when a user abandons checkout (did you know 75% of the time, users abandon checkout when Apple's payment sheet comes up?!).
- Adds `.onSkip` to `PaywallPresentationHandler` which is passed a `PaywallSkippedReason` when a paywall is not supposed to show.
- Adds logging at `INFO` level, mansplaining exactly why a paywall is not shown when calling `register` or `getPaywallViewController`.
- Adds new automatically tracked event `presentation_request` that gets sent with properties explaining why a paywall was or was not shown.
- Adds new automatically tracked event `presentation_request` that gets sent with properties explaining why a paywall was or was not shown.

### Fixes

Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/RevenueCat/purchases-ios.git",
"state": {
"branch": null,
"revision": "a0358d44d84a9d27a632518ee5aa4131b294955f",
"version": "4.17.11"
"revision": "4e0d23e01f58ac0c713585c2f2c498123188e530",
"version": "4.24.0"
}
}
]
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ final class RCPurchaseController: PurchaseController {
}

// MARK: Handle Restores
/// Makes a restore with RevenueCat and returns true, unless an error is thrown.
/// Makes a restore with RevenueCat and returns `.restored`, unless an error is thrown.
/// This gets called when someone tries to restore purchases on one of your paywalls.
func restorePurchases() async -> RestorationResult {
do {
30 changes: 14 additions & 16 deletions Sources/SuperwallKit/Analytics/SessionEventsQueue.swift
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ actor SessionEventsQueue: SessionEnqueuable {
private let maxEventCount = 50
var triggerSessions: [TriggerSession] = []
var transactions: [StoreTransaction] = []
private var timer: AnyCancellable?
private var timer: Timer?
@MainActor
private var willResignActiveObserver: AnyCancellable?
private lazy var lastTwentySessions = LimitedQueue<TriggerSession>(limit: 20)
@@ -45,7 +45,7 @@ actor SessionEventsQueue: SessionEnqueuable {
private unowned let configManager: ConfigManager

deinit {
timer?.cancel()
timer?.invalidate()
timer = nil
}

@@ -65,21 +65,19 @@ actor SessionEventsQueue: SessionEnqueuable {

private func setupTimer() {
let timeInterval = configManager.options.networkEnvironment == .release ? 20.0 : 1.0
timer = Timer
.publish(
every: timeInterval,
on: RunLoop.main,
in: .default
)
.autoconnect()
.sink { [weak self] _ in
guard let self = self else {
return
}
Task {
await self.flushInternal(depth: 10)
}
let timer = Timer(
timeInterval: timeInterval,
repeats: true
) { [weak self] _ in
guard let self = self else {
return
}
Task {
await self.flushInternal(depth: 10)
}
}
self.timer = timer
RunLoop.main.add(timer, forMode: .default)
}

@MainActor
4 changes: 2 additions & 2 deletions Sources/SuperwallKit/Config/ConfigManager.swift
Original file line number Diff line number Diff line change
@@ -67,15 +67,15 @@ class ConfigManager {
private unowned let network: Network
private unowned let paywallManager: PaywallManager

private let factory: RequestFactory & DeviceHelperFactory
private let factory: RequestFactory

init(
options: SuperwallOptions?,
storeKitManager: StoreKitManager,
storage: Storage,
network: Network,
paywallManager: PaywallManager,
factory: RequestFactory & DeviceHelperFactory
factory: RequestFactory
) {
if let options = options {
self.options = options
117 changes: 117 additions & 0 deletions Sources/SuperwallKit/Config/Models/ComputedPropertyRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// File.swift
//
//
// Created by Yusuf Tör on 03/07/2023.
//

import Foundation

/// A request to compute a device property associated with an event at runtime.
@objc(SWKComputedPropertyRequest)
@objcMembers
public final class ComputedPropertyRequest: NSObject, Decodable {
/// The type of device property to compute.
@objc(SWKComputedPropertyRequestType)
public enum ComputedPropertyRequestType: Int, Decodable {
/// The number of minutes since the event occurred.
case minutesSince

/// The number of hours since the event occurred.
case hoursSince

/// The number of days since the event occurred.
case daysSince

/// The number of months since the event occurred.
case monthsSince

/// The number of years since the event occurred.
case yearsSince

var prefix: String {
switch self {
case .minutesSince:
return "minutesSince_"
case .hoursSince:
return "hoursSince_"
case .daysSince:
return "daysSince_"
case .monthsSince:
return "monthsSince_"
case .yearsSince:
return "yearsSince_"
}
}

var calendarComponent: Calendar.Component {
switch self {
case .minutesSince:
return .minute
case .hoursSince:
return .hour
case .daysSince:
return .day
case .monthsSince:
return .month
case .yearsSince:
return .year
}
}

func dateComponent(from components: DateComponents) -> Int? {
switch self {
case .minutesSince:
return components.minute
case .hoursSince:
return components.hour
case .daysSince:
return components.day
case .monthsSince:
return components.month
case .yearsSince:
return components.year
}
}

enum CodingKeys: String, CodingKey {
case minutesSince = "MINUTES_SINCE"
case hoursSince = "HOURS_SINCE"
case daysSince = "DAYS_SINCE"
case monthsSince = "MONTHS_SINCE"
case yearsSince = "YEARS_SINCE"
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
let gatingType = CodingKeys(rawValue: rawValue)
switch gatingType {
case .minutesSince:
self = .minutesSince
case .hoursSince:
self = .hoursSince
case .daysSince:
self = .daysSince
case .monthsSince:
self = .monthsSince
case .yearsSince:
self = .yearsSince
case .none:
throw DecodingError.valueNotFound(
String.self,
.init(
codingPath: [],
debugDescription: "Unsupported computed property type."
)
)
}
}
}

/// The type of device property to compute.
public let type: ComputedPropertyRequestType

/// The name of the event used to compute the device property.
public let eventName: String
}
26 changes: 20 additions & 6 deletions Sources/SuperwallKit/Dependencies/DependencyContainer.swift
Original file line number Diff line number Diff line change
@@ -215,12 +215,19 @@ extension DependencyContainer: ViewControllerFactory {
}

extension DependencyContainer: VariablesFactory {
func makeJsonVariables(productVariables: [ProductVariable]?, params: JSON?) async -> JSON {
let templateDeviceDict = await deviceHelper.getTemplateDevice().dictionary()
func makeJsonVariables(
productVariables: [ProductVariable]?,
computedPropertyRequests: [ComputedPropertyRequest],
event: EventData?
) async -> JSON {
let templateDeviceDict = await deviceHelper.getDeviceAttributes(
since: event,
computedPropertyRequests: computedPropertyRequests
)

return Variables(
productVariables: productVariables,
params: params,
params: event?.parameters,
userAttributes: identityManager.userAttributes,
templateDeviceDictionary: templateDeviceDict
).templated()
@@ -310,14 +317,21 @@ extension DependencyContainer: ApiFactory {

// MARK: - Rule Params
extension DependencyContainer: RuleAttributesFactory {
func makeRuleAttributes() async -> RuleAttributes {
func makeRuleAttributes(
forEvent event: EventData,
from rule: TriggerRule
) async -> RuleAttributes {
var userAttributes = identityManager.userAttributes
userAttributes["isLoggedIn"] = identityManager.isLoggedIn
let device = await deviceHelper.getTemplateDevice().toDictionary()

let deviceAttributes = await deviceHelper.getDeviceAttributes(
since: event,
computedPropertyRequests: rule.computedPropertyRequests
)

return RuleAttributes(
user: userAttributes,
device: device
device: deviceAttributes
)
}
}
8 changes: 6 additions & 2 deletions Sources/SuperwallKit/Dependencies/FactoryProtocols.swift
Original file line number Diff line number Diff line change
@@ -27,7 +27,8 @@ protocol CacheFactory: AnyObject {
protocol VariablesFactory: AnyObject {
func makeJsonVariables(
productVariables: [ProductVariable]?,
params: JSON?
computedPropertyRequests: [ComputedPropertyRequest],
event: EventData?
) async -> JSON
}

@@ -52,7 +53,10 @@ protocol RequestFactory: AnyObject {
}

protocol RuleAttributesFactory: AnyObject {
func makeRuleAttributes() async -> RuleAttributes
func makeRuleAttributes(
forEvent event: EventData,
from rule: TriggerRule
) async -> RuleAttributes
}

protocol TriggerSessionManagerFactory: AnyObject {
Loading

0 comments on commit 72b26fa

Please sign in to comment.