@kontextso/sdk-react-native 3.1.0-rc.4 → 3.2.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/KontextSDK.swift +32 -0
- package/ios/RNKontext.mm +22 -4
- package/ios/SKStoreProductManager.swift +119 -0
- package/package.json +1 -1
- package/src/NativeRNKontext.ts +2 -0
- package/src/services/SkOverlay.ts +1 -4
- package/src/services/SkStoreProduct.ts +20 -0
- package/src/services/utils.ts +4 -0
package/ios/KontextSDK.swift
CHANGED
|
@@ -65,4 +65,36 @@ public class KontextSDK: NSObject {
|
|
|
65
65
|
resolve(ok)
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
// MARK: - SKStoreProductViewController
|
|
70
|
+
|
|
71
|
+
@objc
|
|
72
|
+
public static func presentSKStoreProduct(
|
|
73
|
+
_ appStoreId: String,
|
|
74
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
75
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
76
|
+
) {
|
|
77
|
+
DispatchQueue.main.async {
|
|
78
|
+
SKStoreProductManager.shared.present(appStoreId: appStoreId) { ok, error in
|
|
79
|
+
if ok {
|
|
80
|
+
resolve(true)
|
|
81
|
+
} else if let error = error {
|
|
82
|
+
reject("SK_STORE_PRODUCT_ERROR", error.localizedDescription, error)
|
|
83
|
+
} else {
|
|
84
|
+
resolve(false)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@objc
|
|
91
|
+
public static func dismissSKStoreProduct(
|
|
92
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
93
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
94
|
+
) {
|
|
95
|
+
DispatchQueue.main.async {
|
|
96
|
+
let ok = SKStoreProductManager.shared.dismiss()
|
|
97
|
+
resolve(ok)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
68
100
|
}
|
package/ios/RNKontext.mm
CHANGED
|
@@ -17,7 +17,6 @@ RCT_EXPORT_MODULE()
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// NEW: Present SKOverlay
|
|
21
20
|
- (void)presentSKOverlay:(NSString *)appStoreId
|
|
22
21
|
position:(NSString *)position
|
|
23
22
|
dismissible:(BOOL)dismissible
|
|
@@ -30,12 +29,22 @@ RCT_EXPORT_MODULE()
|
|
|
30
29
|
rejecter:reject];
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
// NEW: Dismiss SKOverlay
|
|
34
32
|
- (void)dismissSKOverlay:(RCTPromiseResolveBlock)resolve
|
|
35
33
|
reject:(RCTPromiseRejectBlock)reject {
|
|
36
34
|
[KontextSDK dismissSKOverlay:resolve rejecter:reject];
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
- (void)presentSKStoreProduct:(NSString *)appStoreId
|
|
38
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
39
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
40
|
+
[KontextSDK presentSKStoreProduct:appStoreId resolver:resolve rejecter:reject];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (void)dismissSKStoreProduct:(RCTPromiseResolveBlock)resolve
|
|
44
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
45
|
+
[KontextSDK dismissSKStoreProduct:resolve rejecter:reject];
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
40
49
|
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
41
50
|
return std::make_shared<facebook::react::NativeRNKontextSpecJSI>(params);
|
|
@@ -54,7 +63,6 @@ RCT_EXPORT_METHOD(isSoundOn : (RCTPromiseResolveBlock)resolve
|
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
// NEW: Present SKOverlay
|
|
58
66
|
RCT_EXPORT_METHOD(presentSKOverlay:(NSString *)appStoreId
|
|
59
67
|
position:(NSString *)position
|
|
60
68
|
dismissible:(BOOL)dismissible
|
|
@@ -67,12 +75,22 @@ RCT_EXPORT_METHOD(presentSKOverlay:(NSString *)appStoreId
|
|
|
67
75
|
rejecter:reject];
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
// NEW: Dismiss SKOverlay
|
|
71
78
|
RCT_EXPORT_METHOD(dismissSKOverlay:(RCTPromiseResolveBlock)resolve
|
|
72
79
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
73
80
|
[KontextSDK dismissSKOverlay:resolve rejecter:reject];
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
RCT_EXPORT_METHOD(presentSKStoreProduct:(NSString *)appStoreId
|
|
84
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
85
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
86
|
+
[KontextSDK presentSKStoreProduct:appStoreId resolver:resolve rejecter:reject];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
RCT_EXPORT_METHOD(dismissSKStoreProduct:(RCTPromiseResolveBlock)resolve
|
|
90
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
91
|
+
[KontextSDK dismissSKStoreProduct:resolve rejecter:reject];
|
|
92
|
+
}
|
|
93
|
+
|
|
76
94
|
#endif
|
|
77
95
|
|
|
78
96
|
@end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import StoreKit
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
final class SKStoreProductManager: NSObject, SKStoreProductViewControllerDelegate {
|
|
6
|
+
static let shared = SKStoreProductManager()
|
|
7
|
+
|
|
8
|
+
private weak var presentedViewController: SKStoreProductViewController?
|
|
9
|
+
|
|
10
|
+
func present(appStoreId: String, completion: @escaping (Bool, NSError?) -> Void) {
|
|
11
|
+
guard let itemId = Int(appStoreId) else {
|
|
12
|
+
completion(false, NSError(
|
|
13
|
+
domain: "SKStoreProduct",
|
|
14
|
+
code: 1,
|
|
15
|
+
userInfo: [NSLocalizedDescriptionKey: "appStoreId must be a valid integer string"]
|
|
16
|
+
))
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let params: [String: Any] = [
|
|
21
|
+
SKStoreProductParameterITunesItemIdentifier: NSNumber(value: itemId)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
let viewController = SKStoreProductViewController()
|
|
25
|
+
viewController.delegate = self
|
|
26
|
+
|
|
27
|
+
viewController.loadProduct(withParameters: params) { [weak self] loaded, error in
|
|
28
|
+
guard let self = self else { return }
|
|
29
|
+
|
|
30
|
+
DispatchQueue.main.async {
|
|
31
|
+
guard loaded else {
|
|
32
|
+
completion(false, NSError(
|
|
33
|
+
domain: "SKStoreProduct",
|
|
34
|
+
code: 2,
|
|
35
|
+
userInfo: [NSLocalizedDescriptionKey: "Failed to load product"]
|
|
36
|
+
))
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
guard let top = self.topViewController() else {
|
|
41
|
+
completion(false, NSError(
|
|
42
|
+
domain: "SKStoreProduct",
|
|
43
|
+
code: 3,
|
|
44
|
+
userInfo: [NSLocalizedDescriptionKey: "No top view controller found"]
|
|
45
|
+
))
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_ = self.dismiss()
|
|
50
|
+
top.present(viewController, animated: true)
|
|
51
|
+
self.presentedViewController = viewController
|
|
52
|
+
completion(true, nil)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@discardableResult
|
|
58
|
+
func dismiss() -> Bool {
|
|
59
|
+
var dismissed = false
|
|
60
|
+
|
|
61
|
+
let run: () -> Void = { [weak self] in
|
|
62
|
+
guard let self = self else { return }
|
|
63
|
+
|
|
64
|
+
if let viewController = self.presentedViewController {
|
|
65
|
+
viewController.dismiss(animated: true) { [weak self] in
|
|
66
|
+
self?.presentedViewController = nil
|
|
67
|
+
}
|
|
68
|
+
dismissed = true
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if let top = self.topViewController(),
|
|
73
|
+
let storeViewController = top.presentedViewController as? SKStoreProductViewController {
|
|
74
|
+
storeViewController.dismiss(animated: true) { [weak self] in
|
|
75
|
+
self?.presentedViewController = nil
|
|
76
|
+
}
|
|
77
|
+
dismissed = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if Thread.isMainThread {
|
|
82
|
+
run()
|
|
83
|
+
} else {
|
|
84
|
+
DispatchQueue.main.sync { run() }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return dismissed
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
|
|
91
|
+
_ = dismiss()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func topViewController(base: UIViewController? = nil) -> UIViewController? {
|
|
95
|
+
let seed: UIViewController? = base ?? {
|
|
96
|
+
if #available(iOS 13.0, *) {
|
|
97
|
+
let scene = UIApplication.shared.connectedScenes
|
|
98
|
+
.compactMap { $0 as? UIWindowScene }
|
|
99
|
+
.first { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }
|
|
100
|
+
return scene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
|
|
101
|
+
} else {
|
|
102
|
+
return UIApplication.shared.keyWindow?.rootViewController
|
|
103
|
+
}
|
|
104
|
+
}()
|
|
105
|
+
|
|
106
|
+
guard let seed = seed else { return nil }
|
|
107
|
+
|
|
108
|
+
if let nav = seed as? UINavigationController {
|
|
109
|
+
return topViewController(base: nav.visibleViewController)
|
|
110
|
+
}
|
|
111
|
+
if let tab = seed as? UITabBarController {
|
|
112
|
+
return topViewController(base: tab.selectedViewController)
|
|
113
|
+
}
|
|
114
|
+
if let presented = seed.presentedViewController {
|
|
115
|
+
return topViewController(base: presented)
|
|
116
|
+
}
|
|
117
|
+
return seed
|
|
118
|
+
}
|
|
119
|
+
}
|
package/package.json
CHANGED
package/src/NativeRNKontext.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface Spec extends TurboModule {
|
|
|
5
5
|
isSoundOn(): Promise<boolean>
|
|
6
6
|
presentSKOverlay(appStoreId: string, position: string, dismissible: boolean): Promise<boolean>
|
|
7
7
|
dismissSKOverlay(): Promise<boolean>
|
|
8
|
+
presentSKStoreProduct(appStoreId: string): Promise<boolean>
|
|
9
|
+
dismissSKStoreProduct(): Promise<boolean>
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default TurboModuleRegistry.getEnforcing<Spec>('RNKontext')
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { Platform } from 'react-native'
|
|
2
2
|
import NativeRNKontext from '../NativeRNKontext'
|
|
3
|
+
import { isValidAppStoreId } from './utils'
|
|
3
4
|
|
|
4
5
|
export type SKOverlayPosition = 'bottom' | 'bottomRaised'
|
|
5
6
|
|
|
6
|
-
const isValidAppStoreId = (id: unknown): id is string => {
|
|
7
|
-
return typeof id === 'string' && /^\d+$/.test(id)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
7
|
const isValidPosition = (p: unknown): p is SKOverlayPosition => {
|
|
11
8
|
return p === 'bottom' || p === 'bottomRaised'
|
|
12
9
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Platform } from 'react-native'
|
|
2
|
+
import NativeRNKontext from '../NativeRNKontext'
|
|
3
|
+
import { isValidAppStoreId } from './utils'
|
|
4
|
+
|
|
5
|
+
export async function presentSKStoreProduct(appStoreId: string) {
|
|
6
|
+
if (Platform.OS !== 'ios') {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
if (!isValidAppStoreId(appStoreId)) {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
return NativeRNKontext.presentSKStoreProduct(appStoreId)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function dismissSKStoreProduct() {
|
|
16
|
+
if (Platform.OS !== 'ios') {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
return NativeRNKontext.dismissSKStoreProduct()
|
|
20
|
+
}
|