Skip to content
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
4 changes: 2 additions & 2 deletions QonversionSandwich.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'QonversionSandwich'
s.version = '7.1.0'
s.version = '7.2.0'
s.summary = 'qonversion.io'
s.swift_version = '5.0'
s.description = <<-DESC
Expand All @@ -16,6 +16,6 @@ Pod::Spec.new do |s|
"osx" => "10.13"
}
s.source_files = 'ios/sandwich/**/*.{h,m,swift}'
s.dependency "Qonversion", "6.0.0"
s.dependency "Qonversion", "6.1.0"
s.module_name = 'QonversionSandwich'
end
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
buildscript {
ext {
release = [
versionName: "7.1.0",
versionName: "7.2.0",
]
}

Expand Down
2 changes: 1 addition & 1 deletion android/sandwich/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.nocodes_version = '1.1.0'
ext.nocodes_version = '1.2.1'
}

plugins {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.qonversion.sandwich

interface NoCodesPurchaseDelegateBridge {
fun purchase(product: BridgeData)
fun restore()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import io.qonversion.nocodes.dto.QScreenPresentationConfig
import io.qonversion.nocodes.dto.QAction
import io.qonversion.nocodes.error.NoCodesError
import androidx.core.content.edit
import com.qonversion.android.sdk.dto.products.QProduct
import io.qonversion.nocodes.interfaces.PurchaseDelegateWithCallbacks

class NoCodesSandwich {

Expand All @@ -22,6 +24,12 @@ class NoCodesSandwich {
return screenPresentationConfigs[contextKey] ?: defaultPresentationConfig ?: QScreenPresentationConfig()
}
}

private var delegatedPurchaseSuccessCallback: PurchaseDelegateWithCallbacks.OnSuccess? = null
private var delegatedPurchaseErrorCallback: PurchaseDelegateWithCallbacks.OnError? = null
private var delegatedRestoreSuccessCallback: PurchaseDelegateWithCallbacks.OnSuccess? = null
private var delegatedRestoreErrorCallback: PurchaseDelegateWithCallbacks.OnError? = null

private lateinit var noCodesDelegate: NoCodesDelegate

// region Initialization
Expand Down Expand Up @@ -94,6 +102,11 @@ class NoCodesSandwich {
}
}

fun setPurchaseDelegate(delegate: NoCodesPurchaseDelegateBridge) {
val purchaseDelegate = createPurchaseDelegate(delegate)
NoCodes.shared.setPurchaseDelegate(purchaseDelegate)
}

// endregion

// region Screen Management
Expand Down Expand Up @@ -121,6 +134,28 @@ class NoCodesSandwich {

// endregion

// region Purchase Management

fun delegatedPurchaseCompleted() {
delegatedPurchaseSuccessCallback?.invoke()
}

fun delegatedPurchaseFailed(errorMessage: String) {
val exception = Exception(errorMessage)
delegatedPurchaseErrorCallback?.invoke(exception)
}

fun delegatedRestoreCompleted() {
delegatedRestoreSuccessCallback?.invoke()
}

fun delegatedRestoreFailed(errorMessage: String) {
val exception = Exception(errorMessage)
delegatedRestoreErrorCallback?.invoke(exception)
}

// endregion

// region Private

private fun createNoCodesDelegate(eventListener: NoCodesEventListener): NoCodesDelegate {
Expand Down Expand Up @@ -152,5 +187,28 @@ class NoCodesSandwich {
}
}

private fun createPurchaseDelegate(delegate: NoCodesPurchaseDelegateBridge): PurchaseDelegateWithCallbacks {
return object : PurchaseDelegateWithCallbacks {
override fun purchase(
product: QProduct,
onSuccess: PurchaseDelegateWithCallbacks.OnSuccess,
onError: PurchaseDelegateWithCallbacks.OnError
) {
delegatedPurchaseSuccessCallback = onSuccess
delegatedPurchaseErrorCallback = onError
delegate.purchase(product.toMap())
}

override fun restore(
onSuccess: PurchaseDelegateWithCallbacks.OnSuccess,
onError: PurchaseDelegateWithCallbacks.OnError
) {
delegatedRestoreSuccessCallback = onSuccess
delegatedRestoreErrorCallback = onError
delegate.restore()
}
}
}

// endregion
}
14 changes: 7 additions & 7 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PODS:
- Qonversion (6.0.0):
- Qonversion/Main (= 6.0.0)
- Qonversion/Main (6.0.0)
- QonversionSandwich (6.0.11):
- Qonversion (= 6.0.0)
- Qonversion (6.1.0):
- Qonversion/Main (= 6.1.0)
- Qonversion/Main (6.1.0)
- QonversionSandwich (7.1.0):
- Qonversion (= 6.1.0)

DEPENDENCIES:
- QonversionSandwich (from `../`)
Expand All @@ -17,8 +17,8 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
Qonversion: 694b88c4bfc06d827c189908e73ea955a1ddde6c
QonversionSandwich: e8c033177c8a89f882fb7bcc821af0819d367c40
Qonversion: 0b64e731cb59f94ab03c132cea2b70552b4dcc2b
QonversionSandwich: 1b9f83ed758c0a9289e09981b82c6e9fc65e7fa4

PODFILE CHECKSUM: e5fb0f1b622edd873d42d0bef64736d62943be41

Expand Down
4 changes: 4 additions & 0 deletions ios/QonversionSandwich.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
6A439CA42807112400019344 /* SandwichError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A439CA32807112400019344 /* SandwichError.swift */; };
6A76B6782ED9C5770063B4DE /* NoCodesPurchaseDelegateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A76B6762ED9C5470063B4DE /* NoCodesPurchaseDelegateBridge.swift */; };
6A8EB5D128046BAA00DCCD9E /* Mappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8EB5D028046BAA00DCCD9E /* Mappers.swift */; };
6ACDA8F6280586460003AC8F /* QonversionSandwich.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ACDA8F5280586460003AC8F /* QonversionSandwich.swift */; };
6ACDA8F8280587A20003AC8F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ACDA8F7280587A20003AC8F /* Constants.swift */; };
Expand Down Expand Up @@ -38,6 +39,7 @@
2978A890248F68C3F7FC8AB4 /* Pods_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
670A4F21CF8A1ECAF5528B99 /* Pods_Sample_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Sample_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6A439CA32807112400019344 /* SandwichError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandwichError.swift; sourceTree = "<group>"; };
6A76B6762ED9C5470063B4DE /* NoCodesPurchaseDelegateBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCodesPurchaseDelegateBridge.swift; sourceTree = "<group>"; };
6A8EB5C627FD896400DCCD9E /* QonversionSandwich.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QonversionSandwich.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6A8EB5D028046BAA00DCCD9E /* Mappers.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Mappers.swift; sourceTree = "<group>"; tabWidth = 2; };
6ACDA8F5280586460003AC8F /* QonversionSandwich.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = QonversionSandwich.swift; sourceTree = "<group>"; tabWidth = 2; };
Expand Down Expand Up @@ -119,6 +121,7 @@
6ACDA8F92806D36F0003AC8F /* QonversionEventListener.swift */,
6A439CA32807112400019344 /* SandwichError.swift */,
6ACDA9012806DE9F0003AC8F /* CompletionHandlers.swift */,
6A76B6762ED9C5470063B4DE /* NoCodesPurchaseDelegateBridge.swift */,
);
path = sandwich;
sourceTree = "<group>";
Expand Down Expand Up @@ -334,6 +337,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6A76B6782ED9C5770063B4DE /* NoCodesPurchaseDelegateBridge.swift in Sources */,
6ACDA8FA2806D36F0003AC8F /* QonversionEventListener.swift in Sources */,
6A439CA42807112400019344 /* SandwichError.swift in Sources */,
706C3C472DCCF0EE00AF131F /* NoCodesEventListener.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<key>Sample.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>8</integer>
<integer>5</integer>
</dict>
<key>sandwich.xcscheme_^#shared#^_</key>
<dict>
Expand Down
14 changes: 14 additions & 0 deletions ios/sandwich/NoCodesPurchaseDelegateBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// NoCodesPurchaseDelegateBridge.swift
// QonversionSandwich
//
// Created by Kamo Spertsyan on 28.11.2025.
// Copyright © 2025 Qonversion Inc. All rights reserved.
//

import Foundation

@objc public protocol NoCodesPurchaseDelegateBridge {
@objc func purchase(_ product: [String: Any])
@objc func restore()
}
67 changes: 67 additions & 0 deletions ios/sandwich/NoCodesSandwich.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public class NoCodesSandwich: NSObject {
private var defaultPresentationConfig: NoCodesPresentationConfiguration? = nil
private var screenPresentationConfigs: [String: NoCodesPresentationConfiguration] = [:]
private var isCustomizationDelegateSet = false
private var purchaseDelegateBridge: NoCodesPurchaseDelegateBridge?
private var purchaseContinuation: CheckedContinuation<Void, Error>?
private var restoreContinuation: CheckedContinuation<Void, Error>?

@objc public init(noCodesEventListener: NoCodesEventListener) {
self.noCodesEventListener = noCodesEventListener
Expand Down Expand Up @@ -71,6 +74,45 @@ public class NoCodesSandwich: NSObject {

return availableEvents.map { $0.rawValue }
}

@objc public func setPurchaseDelegate(_ delegate: NoCodesPurchaseDelegateBridge) {
purchaseDelegateBridge = delegate
NoCodes.shared.set(purchaseDelegate: self)
}

@objc public func delegatedPurchaseCompleted() {
guard let continuation = purchaseContinuation else { return }
purchaseContinuation = nil
continuation.resume(returning: ())
}

@objc public func delegatedPurchaseFailed(_ errorMessage: String) {
let error = NSError(
domain: "NoCodesBridge",
code: -1,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
)
guard let continuation = purchaseContinuation else { return }
purchaseContinuation = nil
continuation.resume(throwing: error)
}

@objc public func delegatedRestoreCompleted() {
guard let continuation = restoreContinuation else { return }
restoreContinuation = nil
continuation.resume(returning: ())
}

@objc public func delegatedRestoreFailed(_ errorMessage: String) {
let error = NSError(
domain: "NoCodesBridge",
code: -1,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
)
guard let continuation = restoreContinuation else { return }
restoreContinuation = nil
continuation.resume(throwing: error)
}
}

extension NoCodesSandwich: NoCodesScreenCustomizationDelegate {
Expand Down Expand Up @@ -121,4 +163,29 @@ extension NoCodesSandwich: NoCodesDelegate {
noCodesEventListener?.noCodesDidTrigger(event: NoCodesEvent.screenFailedToLoad.rawValue, payload: errorToMap(error)?.clearEmptyValues())
}
}

extension NoCodesSandwich: NoCodesPurchaseDelegate {
public func purchase(product: Qonversion.Product) async throws {
guard let delegate = purchaseDelegateBridge else { return }

try await withCheckedThrowingContinuation { continuation in
purchaseContinuation = continuation
Task { @MainActor in
delegate.purchase(product.toMap().clearEmptyValues())
}
}
}

public func restore() async throws {
guard let delegate = purchaseDelegateBridge else { return }

try await withCheckedThrowingContinuation { continuation in
restoreContinuation = continuation
Task { @MainActor in
delegate.restore()
}
}
}
}

#endif