com.azerion.bluestack 6.0.0-beta.3 → 6.0.0-beta.5

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## [6.0.0-beta.3] - 2026-06-10
3
+ ## [6.0.0-beta.4] - 2026-06-15
4
4
 
5
5
  ### Native SDK upgrades
6
6
 
@@ -19,50 +19,50 @@
19
19
  `BlueStackAds.IsDebugModeEnabled` to read the cached state.
20
20
  - `BlueStackAds.IsInitialized` property to check native-side readiness.
21
21
  - `InterstitialAd.IsReady` and `RewardedAd.IsReady` properties.
22
- - `BannerAd` anchor-based positioning — new constructor
23
- `BannerAd(string placementId, AdSize adSize, Transform anchor, Camera camera = null)`. The banner
24
- follows the anchor's screen position automatically via the new `AdPlacementHandler` component.
25
- The anchor's screen-space position is treated as the banner's **top-left corner** (matching
26
- native iOS / Android frame placement).
27
- - `BannerAd` custom screen-space position constructor — `BannerAd(string placementId, AdSize adSize, Vector2 position)`.
22
+ - A new `BannerAd` constructor for anchor-based positioning,
23
+ `BannerAd(string placementId, AdSize adSize, Transform anchor, Camera camera = null)`. The banner
24
+ follows the anchor's screen position automatically via the `AdPlacementHandler` component.
25
+ The anchor's screen-space position is treated as the banner's **top-left corner** (matching
26
+ native iOS / Android frame placement).
27
+ - A new `BannerAd` constructor for custom screen-space positioning,
28
+ `BannerAd(string placementId, AdSize adSize, Vector2 position)`.
28
29
  The `position` is the banner's **top-left corner** in Unity pixels (bottom-left origin).
29
- - Optional `useSafeArea` parameter on `BannerAd(string, AdSize, AdPosition, bool useSafeArea = true)`
30
+ - Optional `useSafeArea` parameter on `BannerAd(string placementId, AdSize adSize, AdPosition adPosition, bool useSafeArea = true)`
30
31
  to opt out of safe-area insets for sticky Top/Bottom banners. Custom-positioned banners always
31
32
  ignore the safe area.
32
- - Banner masking `BannerAd.SetMask(RectTransform, Camera)` / `BannerAd.RemoveMask()` clip the banner
33
- to a UI region. The mask follows the `RectTransform` as it animates or resizes.
33
+ - Banner masking, `BannerAd.SetMask(RectTransform, Camera)` / `BannerAd.RemoveMask()` clip the banner
34
+ to a UI region. The mask follows the `RectTransform` as it animates or resizes via the `AdMaskHandler` component.
34
35
  - `BannerAd.OnAdResized` event delivers a `PreferredBannerSize` (`Width`, `Height`) whenever the SDK
35
36
  reports a post-load size change.
36
37
  - `BannerAd.OnAdLoaded` payload changed to `PreferredBannerSize` carrying the SDK-preferred dimensions
37
38
  (was `EventArgs`).
38
39
  - `RewardedAd.OnAdRewardEarned` (renamed from `OnRewardEarned`) delivers a `RewardedItem`.
39
- - `UpdateInterval` and `ForceUpdate` API added to `AdMaskHandler` and `AdPlacementHandler`
40
40
 
41
41
  ### Changed
42
42
 
43
- - **Breaking** `BlueStackAds.Initialize` signature simplified:
43
+ - **Breaking**: `BlueStackAds.Initialize` signature simplified:
44
44
  - Before: `Initialize(string appId, Settings settings, Action<SDKInitializationStatus> sdkCallback, Action<IInitializationStatusClient> adaptersCallback)`
45
45
  - After: `Initialize(string appId, Action<InitializationStatus> initializationCompleteAction = null)`
46
- - The `Settings`-based debug toggle is replaced by `BlueStackAds.SetDebugMode(bool)`.
46
+ - The `Settings` based debug toggle is replaced by `BlueStackAds.SetDebugMode(bool)`.
47
47
  - The separate success/fail `SDKInitializationStatus` callback is removed. The single completion
48
48
  callback now delivers `InitializationStatus.AdapterStatusMap` (per-adapter `AdapterStatus`).
49
49
  - The initialization callback is now consistently dispatched to the Unity main thread on both
50
50
  iOS and Android.
51
- - **Breaking** `RewardedVideoAd` class renamed to `RewardedAd`.
52
- - **Breaking** Banner event names renamed:
51
+ - **Breaking**: `RewardedVideoAd` class renamed to `RewardedAd`.
52
+ - **Breaking**: Banner event names renamed:
53
53
  - `OnBannerDidLoad` → `OnAdLoaded` (payload changed to `PreferredBannerSize`)
54
54
  - `OnBannerDidFailed` → `OnAdFailedToLoad`
55
55
  - `OnBannerDisplay` → `OnAdDisplayed`
56
56
  - `OnBannerHide` → `OnAdHidden`
57
57
  - `OnBannerDidRefresh` → `OnAdRefreshed`
58
58
  - `OnBannerDidFailToRefresh` → `OnAdFailedToRefresh`
59
- - **Breaking** Interstitial event names renamed:
59
+ - **Breaking**: Interstitial event names renamed:
60
60
  - `OnInterstitialDidLoaded` → `OnAdLoaded`
61
61
  - `OnInterstitialDidFail` → `OnAdFailedToLoad`
62
62
  - `OnInterstitialClicked` → `OnAdClicked`
63
63
  - `OnInterstitialDidShown` → `OnAdDisplayed`
64
64
  - `OnInterstitialDisappear` → `OnAdDismissed`
65
- - **Breaking** Rewarded event names renamed:
65
+ - **Breaking**: Rewarded event names renamed:
66
66
  - `OnRewardedVideoAdLoaded` → `OnAdLoaded`
67
67
  - `OnRewardedVideoAdError` → `OnAdFailedToLoad`
68
68
  - `OnRewardedVideoAdAppeared` → `OnAdDisplayed`
@@ -70,30 +70,27 @@
70
70
  - `OnRewardedVideoAdClosed` → `OnAdDismissed`
71
71
  - `OnUserRewardEarned` → `OnAdRewardEarned`
72
72
  - To display banner ads, `BannerAd.Show()` must be called after the ad has been loaded.
73
- - **Breaking** `BannerAd` constructors now take `AdSize` as a mandatory second parameter, and
73
+ - **Breaking**: `BannerAd` constructors now take `AdSize` as a mandatory second parameter, and
74
74
  `BannerAd.Load` no longer takes one:
75
75
  - Before: `new BannerAd(placementId, adPosition); bannerAd.Load(adSize);`
76
76
  - After: `new BannerAd(placementId, adSize, adPosition); bannerAd.Load();`
77
77
  - Applies to all three constructor shapes (`AdPosition`, `Vector2`, `Transform` anchor).
78
78
  - `BannerAd.Load(AdSize)` and `BannerAd.Load(AdSize, Preference)` are replaced by parameterless
79
- `Load()` and `Load(Preference)` the ad size is now fixed for the lifetime of the `BannerAd`.
80
- - **Breaking** `Preference` class renamed to `RequestOptions` and made **immutable**.
79
+ `Load()` and `Load(Preference)`, the ad size is now fixed for the lifetime of the `BannerAd`.
80
+ - **Breaking**: `Preference` class is replaced with `RequestOptions` and made **immutable**.
81
81
  - Mutable `Set*` builder methods are replaced by a single optional-named-args constructor:
82
82
  `new RequestOptions(age: 25, gender: Gender.Male, location: ..., consentFlag: 1,
83
83
  language: "en", keyword: "...", contentUrl: "...")`.
84
84
  - A single `RequestOptions` instance is safe to reuse across multiple `Load(...)` calls.
85
- - **Breaking** `Location` made immutable:
85
+ - **Breaking**: `Location` made immutable:
86
86
  - Constructor now `new Location(double latitude, double longitude, string provider = NONE_PROVIDER)`
87
87
  instead of `new Location(provider)` + mutable `Latitude` / `Longitude` setters.
88
88
  - `getProvider()` method replaced by a `Provider` get-only property.
89
89
 
90
90
  ### Removed
91
91
 
92
- - **Breaking** `Settings` parameter removed from `BlueStackAds.Initialize`. Use `BlueStackAds.SetDebugMode(bool)` instead of
92
+ - **Breaking**: `Settings` parameter removed from `BlueStackAds.Initialize`. Use `BlueStackAds.SetDebugMode(bool)` instead of
93
93
  `new Settings(isDebugModeEnabled: ...)`.
94
- - **Breaking** — `Preference.Destroy()` removed — the new immutable `RequestOptions` holds no
95
- native resources on the Unity side; the native intermediate is constructed and released inside
96
- each `Load(...)` call.
97
94
 
98
95
  ## [3.4.1] - 2026-02-27
99
96
 
@@ -1,4 +1,4 @@
1
- <dependencies version="6.0.0-beta.3">
1
+ <dependencies version="6.0.0-beta.5">
2
2
  <androidPackages>
3
3
  <androidPackage alias="BlueStack" spec="com.azerion:bluestack-sdk-core:6.0.1">
4
4
  <repositories>
@@ -59,7 +59,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
59
59
 
60
60
 
61
61
  private func createBannerAd() {
62
- let initializeBannerFactory = {
62
+ BSUMainThreadDispatcher.run {
63
63
  self.destroy()
64
64
  guard let bannerAdSize = self.bannerAdSize else { return }
65
65
  self.bannerAdsFactory = BannerView(adSize: bannerAdSize.size)
@@ -69,55 +69,31 @@ public class BSUBanner: NSObject, BannerViewDelegate {
69
69
  // Auto refresh is on by default in the SDK and can be controlled from the console
70
70
  // self.bannerAdsFactory?.startAutoRefresh()
71
71
  }
72
-
73
- if Thread.isMainThread {
74
- initializeBannerFactory()
75
- } else {
76
- DispatchQueue.main.async {
77
- initializeBannerFactory()
78
- }
79
- }
80
72
  }
81
73
 
82
74
  @objc public func loadAd() {
83
- if Thread.isMainThread {
84
- self.loadBannerAd(requestOptions: nil)
85
- } else {
86
- DispatchQueue.main.async { [weak self] in
87
- self?.loadBannerAd(requestOptions: nil)
88
- }
75
+ BSUMainThreadDispatcher.run { [weak self] in
76
+ self?.loadBannerAd(requestOptions: nil)
89
77
  }
90
78
  }
91
79
 
92
80
  @objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
93
81
  let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
94
82
  let sdkOptions = bsuOptions.toSDKRequestOptions()
95
- if Thread.isMainThread {
96
- self.loadBannerAd(requestOptions: sdkOptions)
97
- } else {
98
- DispatchQueue.main.async { [weak self] in
99
- self?.loadBannerAd(requestOptions: sdkOptions)
100
- }
83
+ BSUMainThreadDispatcher.run { [weak self] in
84
+ self?.loadBannerAd(requestOptions: sdkOptions)
101
85
  }
102
86
  }
103
87
 
104
88
  @objc public func show() {
105
- if Thread.isMainThread {
106
- showBannerAd()
107
- } else {
108
- DispatchQueue.main.async { [weak self] in
109
- self?.showBannerAd()
110
- }
89
+ BSUMainThreadDispatcher.run { [weak self] in
90
+ self?.showBannerAd()
111
91
  }
112
92
  }
113
93
 
114
94
  @objc public func hide() {
115
- if Thread.isMainThread {
116
- hideBannerAd()
117
- } else {
118
- DispatchQueue.main.async { [weak self] in
119
- self?.hideBannerAd()
120
- }
95
+ BSUMainThreadDispatcher.run { [weak self] in
96
+ self?.hideBannerAd()
121
97
  }
122
98
  }
123
99
 
@@ -151,8 +127,11 @@ public class BSUBanner: NSObject, BannerViewDelegate {
151
127
  // Switching from Auto Layout to frame-based positioning
152
128
  self.switchPositioningMode()
153
129
  } else {
154
- // Already in custom mode, just update position (fast path)
155
- self.updateCustomPosition()
130
+ // Already in custom mode, just update position (fast path).
131
+ // Guarded because this path is hit on every position update from Unity.
132
+ BSUMainThreadDispatcher.run { [weak self] in
133
+ self?.updateCustomPosition()
134
+ }
156
135
  }
157
136
  }
158
137
 
@@ -202,7 +181,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
202
181
  self.updateCustomPosition()
203
182
  }
204
183
  }
205
- if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
184
+ BSUMainThreadDispatcher.run(work)
206
185
  }
207
186
 
208
187
  /// Removes the `MaskView` and moves `bannerContainerView` back to `unityView`.
@@ -227,7 +206,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
227
206
  self.updateCustomPosition()
228
207
  }
229
208
  }
230
- if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
209
+ BSUMainThreadDispatcher.run(work)
231
210
  }
232
211
 
233
212
  // MARK: - Private Methods
@@ -248,20 +227,16 @@ public class BSUBanner: NSObject, BannerViewDelegate {
248
227
  self.updateCustomPosition()
249
228
  }
250
229
  }
251
- if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
230
+ BSUMainThreadDispatcher.run(work)
252
231
  }
253
-
232
+
254
233
  // bannerContainerView Positioning
255
234
  // - Auto Layout: Used for .topOfScreen and .bottomOfScreen (set once, iOS manages)
256
235
  // - Frame-based: Used for .custom positions (direct frame manipulation for frequent updates)
257
236
 
258
237
  private func switchPositioningMode() {
259
- if Thread.isMainThread {
260
- self.switchPositioningModeInternal()
261
- } else {
262
- DispatchQueue.main.async { [weak self] in
263
- self?.switchPositioningModeInternal()
264
- }
238
+ BSUMainThreadDispatcher.run { [weak self] in
239
+ self?.switchPositioningModeInternal()
265
240
  }
266
241
  }
267
242
 
@@ -277,21 +252,13 @@ public class BSUBanner: NSObject, BannerViewDelegate {
277
252
  }
278
253
 
279
254
  private func positionBannerView() {
280
- if Thread.isMainThread {
281
- if adPosition == .custom {
255
+ BSUMainThreadDispatcher.run { [weak self] in
256
+ guard let self = self else { return }
257
+ if self.adPosition == .custom {
282
258
  self.updateCustomPosition()
283
259
  } else {
284
260
  self.updateAutoLayoutPosition()
285
261
  }
286
- } else {
287
- DispatchQueue.main.async { [weak self] in
288
- guard let self = self else { return }
289
- if self.adPosition == .custom {
290
- self.updateCustomPosition()
291
- } else {
292
- self.updateAutoLayoutPosition()
293
- }
294
- }
295
262
  }
296
263
  }
297
264
 
@@ -510,46 +477,28 @@ public class BSUBanner: NSObject, BannerViewDelegate {
510
477
 
511
478
  private func removeBannerView() {
512
479
  guard let _ = self.bannerView else { return }
513
-
514
- if Thread.isMainThread {
515
- self.bannerView?.removeFromSuperview()
516
- self.bannerView = nil
517
- } else {
518
- DispatchQueue.main.async { [weak self] in
519
- guard let strongSelf = self else { return }
520
- strongSelf.bannerView?.removeFromSuperview()
521
- strongSelf.bannerView = nil
522
- }
480
+ BSUMainThreadDispatcher.run { [weak self] in
481
+ guard let strongSelf = self else { return }
482
+ strongSelf.bannerView?.removeFromSuperview()
483
+ strongSelf.bannerView = nil
523
484
  }
524
485
  }
525
486
 
526
487
  private func removeBannerContainerView() {
527
488
  guard let _ = self.bannerContainerView else { return }
528
-
529
- if Thread.isMainThread {
530
- self.bannerContainerView?.removeFromSuperview()
531
- self.bannerContainerView = nil
532
- } else {
533
- DispatchQueue.main.async { [weak self] in
534
- guard let strongSelf = self else { return }
535
- strongSelf.bannerContainerView?.removeFromSuperview()
536
- strongSelf.bannerContainerView = nil
537
- }
489
+ BSUMainThreadDispatcher.run { [weak self] in
490
+ guard let strongSelf = self else { return }
491
+ strongSelf.bannerContainerView?.removeFromSuperview()
492
+ strongSelf.bannerContainerView = nil
538
493
  }
539
494
  }
540
-
495
+
541
496
  private func removeBannerMaskView() {
542
497
  guard let _ = self.bannerMaskView else { return }
543
-
544
- if Thread.isMainThread {
545
- self.bannerMaskView?.removeFromSuperview()
546
- self.bannerMaskView = nil
547
- } else {
548
- DispatchQueue.main.async { [weak self] in
549
- guard let strongSelf = self else { return }
550
- strongSelf.bannerMaskView?.removeFromSuperview()
551
- strongSelf.bannerMaskView = nil
552
- }
498
+ BSUMainThreadDispatcher.run { [weak self] in
499
+ guard let strongSelf = self else { return }
500
+ strongSelf.bannerMaskView?.removeFromSuperview()
501
+ strongSelf.bannerMaskView = nil
553
502
  }
554
503
  }
555
504
  // MARK: - Constraint References (only used for Auto Layout mode)
@@ -602,37 +551,35 @@ public class BSUBanner: NSObject, BannerViewDelegate {
602
551
  // MARK: - BannerViewDelegate
603
552
 
604
553
  public func onLoad(_ bannerView: BlueStackSDK.BannerView, _ preferredHeight: CGFloat) {
605
- // print("Banner successfully loaded with preferredHeight: \(preferredHeight)")
606
- self.removeBannerView()
607
- self.bannerView = bannerView
608
- self.bannerContainerView?.addSubview(bannerView)
609
- self.bannerView?.translatesAutoresizingMaskIntoConstraints = false
610
-
611
- // var target = CGSize(width: adFrame.width, height: preferredHeight)
612
-
613
- // Resolve the final container size:
614
- // - height: always the SDK-reported preferredHeight
615
- // - width: prefer bannerView.frame.width if non-zero, else originally requested width
616
- let resolvedWidth = bannerView.frame.size.width
617
- let target = CGSize(
618
- width: resolvedWidth > 0 ? resolvedWidth : adFrame.width,
619
- height: preferredHeight
620
- )
621
- if let containerView = self.bannerContainerView {
622
- self.setupBannerViewConstraints(with: containerView)
623
- containerView.layoutIfNeeded()
624
- adjustContainerSize(to: target)
625
- }
626
- BSULogger.debug("Banner", message: "ad loaded \(Int(target.width))x\(Int(target.height))")
627
- // Forward the final container size (preferredWidth, preferredHeight) to Unity.
628
- self.onBannerDidLoadCallback?(
629
- self.bannerClient,
630
- Int32(target.width),
631
- Int32(target.height)
632
- )
633
-
634
- // Auto show on load
635
- // showBannerAd()
554
+ // Delegate callback may arrive off the main thread; all work below touches UIKit.
555
+ BSUMainThreadDispatcher.run { [weak self] in
556
+ guard let self = self else { return }
557
+ self.removeBannerView()
558
+ self.bannerView = bannerView
559
+ self.bannerContainerView?.addSubview(bannerView)
560
+ self.bannerView?.translatesAutoresizingMaskIntoConstraints = false
561
+
562
+ // Resolve the final container size:
563
+ // - height: always the SDK-reported preferredHeight
564
+ // - width: prefer bannerView.frame.width if non-zero, else originally requested width
565
+ let resolvedWidth = bannerView.frame.size.width
566
+ let target = CGSize(
567
+ width: resolvedWidth > 0 ? resolvedWidth : self.adFrame.width,
568
+ height: preferredHeight
569
+ )
570
+ if let containerView = self.bannerContainerView {
571
+ self.setupBannerViewConstraints(with: containerView)
572
+ containerView.layoutIfNeeded()
573
+ self.adjustContainerSize(to: target)
574
+ }
575
+ BSULogger.debug("Banner", message: "ad loaded \(Int(target.width))x\(Int(target.height))")
576
+ // Forward the final container size (preferredWidth, preferredHeight) to Unity.
577
+ self.onBannerDidLoadCallback?(
578
+ self.bannerClient,
579
+ Int32(target.width),
580
+ Int32(target.height)
581
+ )
582
+ }
636
583
  }
637
584
 
638
585
  public func onFailedToLoad(_ bannerView: BlueStackSDK.BannerView, _ error: any Error) {
@@ -666,15 +613,19 @@ public class BSUBanner: NSObject, BannerViewDelegate {
666
613
  }
667
614
 
668
615
  public func onResize(_ bannerView: BlueStackSDK.BannerView, _ size: CGSize) {
669
- BSULogger.debug("Banner", message: "ad resized")
670
- adjustContainerSize(to: size)
671
- // Forward the final size (preferredWidth, preferredHeight) to Unity.
672
- // adFrame is updated by adjustContainerSize and reflects the size actually applied.
673
- self.onBannerResizeCallback?(
674
- self.bannerClient,
675
- Int32(adFrame.width),
676
- Int32(adFrame.height)
677
- )
616
+ // Delegate callback may arrive off the main thread; adjustContainerSize touches UIKit.
617
+ BSUMainThreadDispatcher.run { [weak self] in
618
+ guard let self = self else { return }
619
+ BSULogger.debug("Banner", message: "ad resized")
620
+ self.adjustContainerSize(to: size)
621
+ // Forward the final size (preferredWidth, preferredHeight) to Unity.
622
+ // adFrame is updated by adjustContainerSize and reflects the size actually applied.
623
+ self.onBannerResizeCallback?(
624
+ self.bannerClient,
625
+ Int32(self.adFrame.width),
626
+ Int32(self.adFrame.height)
627
+ )
628
+ }
678
629
  }
679
630
 
680
631
  /// Updates `adFrame` and Resizes `bannerContainerView` so it wraps `bannerView` exactly.
@@ -32,24 +32,24 @@ public class BSUInterstitialAd: NSObject {
32
32
  }
33
33
 
34
34
  @objc public func loadAd() {
35
- DispatchQueue.main.async {
36
- self.interstitialAdFactory?.load()
35
+ BSUMainThreadDispatcher.run { [weak self] in
36
+ self?.interstitialAdFactory?.load()
37
37
  }
38
38
  }
39
39
 
40
40
  @objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
41
41
  let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
42
42
  let sdkOptions = bsuOptions.toSDKRequestOptions()
43
- DispatchQueue.main.async {
44
- self.interstitialAdFactory?.load(requestOptions: sdkOptions)
43
+ BSUMainThreadDispatcher.run { [weak self] in
44
+ self?.interstitialAdFactory?.load(requestOptions: sdkOptions)
45
45
  }
46
46
  }
47
47
 
48
48
  @objc public func show() {
49
49
  if let interstitialAd = self.interstitialAdFactory {
50
50
  if interstitialAd.isReady {
51
- DispatchQueue.main.async {
52
- interstitialAd.show(fromRootViewController: self.interstitialAdFactory?.viewController)
51
+ BSUMainThreadDispatcher.run { [weak self] in
52
+ interstitialAd.show(fromRootViewController: self?.interstitialAdFactory?.viewController)
53
53
  }
54
54
  } else {
55
55
  BSULogger.warning("Interstitial", message: "cannot show: ad not ready")
@@ -0,0 +1,25 @@
1
+ //
2
+ // BSUMainThreadDispatcher.swift
3
+ // UnityFramework
4
+ //
5
+ // Encapsulates the "run on the main thread" guard used across the iOS bridge.
6
+ // WebView (WKWebView) and UIKit must only be touched on the main thread.
7
+ //
8
+
9
+ import Foundation
10
+
11
+ @objc(BSUMainThreadDispatcher)
12
+ public class BSUMainThreadDispatcher: NSObject {
13
+ /// Executes `work` on the main thread.
14
+ /// Runs synchronously when the caller is already on the main thread,
15
+ /// otherwise hops to the main queue async.
16
+ @objc public static func run(_ work: @escaping () -> Void) {
17
+ if Thread.isMainThread {
18
+ work()
19
+ } else {
20
+ DispatchQueue.main.async {
21
+ work()
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,37 @@
1
+ fileFormatVersion: 2
2
+ guid: 7bdea4bf7c6344c3f9bcb393fa36d210
3
+ PluginImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ iconMap: {}
7
+ executionOrder: {}
8
+ defineConstraints: []
9
+ isPreloaded: 0
10
+ isOverridable: 1
11
+ isExplicitlyReferenced: 0
12
+ validateReferences: 1
13
+ platformData:
14
+ - first:
15
+ Any:
16
+ second:
17
+ enabled: 0
18
+ settings: {}
19
+ - first:
20
+ Editor: Editor
21
+ second:
22
+ enabled: 0
23
+ settings:
24
+ DefaultValueInitialized: true
25
+ - first:
26
+ iPhone: iOS
27
+ second:
28
+ enabled: 1
29
+ settings: {}
30
+ - first:
31
+ tvOS: tvOS
32
+ second:
33
+ enabled: 1
34
+ settings: {}
35
+ userData:
36
+ assetBundleName:
37
+ assetBundleVariant:
@@ -40,29 +40,37 @@ public class BSUNativeAd: NSObject, AdsAdapterNativeDelegate, AdClickDelegate, N
40
40
  }
41
41
 
42
42
  @objc public func loadAd() {
43
- nativeAdsFactory?.loadNative()
43
+ BSUMainThreadDispatcher.run { [weak self] in
44
+ self?.nativeAdsFactory?.loadNative()
45
+ }
44
46
  }
45
47
 
46
48
  @objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
47
49
  let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
48
50
  let sdkOptions = bsuOptions.toSDKRequestOptions()
49
-
51
+
50
52
  // RequestOptions to Preference
51
53
  var requestOptionsToPreferenceTransformer = RequestOptionsToPreferenceTransformer()
52
54
  let preference = requestOptionsToPreferenceTransformer.transform(sdkOptions)
53
-
54
- nativeAdsFactory?.loadNative(withPreferences: preference, withCover: false)
55
+
56
+ BSUMainThreadDispatcher.run { [weak self] in
57
+ self?.nativeAdsFactory?.loadNative(withPreferences: preference, withCover: false)
58
+ }
55
59
  }
56
60
 
57
61
  @objc public func recordImpression(_ args: [AnyHashable: Any]) {
58
- if let nativeAdsObject = self.nativeAdsObject {
62
+ // Drives the OMID/measurement session and WebView, so it must run on the main thread.
63
+ BSUMainThreadDispatcher.run { [weak self] in
64
+ guard let self = self, let nativeAdsObject = self.nativeAdsObject else { return }
59
65
  nativeAdsObject.setNativeAdImpressionListener(self)
60
66
  nativeAdsObject.recordImpression(args)
61
67
  }
62
68
  }
63
69
 
64
70
  @objc public func performClick(_ args: [AnyHashable: Any]) {
65
- if let nativeAdsObject = self.nativeAdsObject {
71
+ // Routes through the OMID session like recordImpression, so it must run on the main thread.
72
+ BSUMainThreadDispatcher.run { [weak self] in
73
+ guard let nativeAdsObject = self?.nativeAdsObject else { return }
66
74
  nativeAdsObject.performClick(args)
67
75
  }
68
76
  }
@@ -32,24 +32,24 @@ public class BSURewardedAd: NSObject {
32
32
  }
33
33
 
34
34
  @objc public func loadAd() {
35
- DispatchQueue.main.async {
36
- self.rewardedAdFactory?.load()
35
+ BSUMainThreadDispatcher.run { [weak self] in
36
+ self?.rewardedAdFactory?.load()
37
37
  }
38
38
  }
39
39
 
40
40
  @objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
41
41
  let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
42
42
  let sdkOptions = bsuOptions.toSDKRequestOptions()
43
- DispatchQueue.main.async {
44
- self.rewardedAdFactory?.load(requestOptions: sdkOptions)
43
+ BSUMainThreadDispatcher.run { [weak self] in
44
+ self?.rewardedAdFactory?.load(requestOptions: sdkOptions)
45
45
  }
46
46
  }
47
-
47
+
48
48
  @objc public func show() {
49
49
  if let rewardedAd = self.rewardedAdFactory {
50
50
  if rewardedAd.isReady {
51
- DispatchQueue.main.async {
52
- rewardedAd.show(fromRootViewController: self.rewardedAdFactory?.viewController)
51
+ BSUMainThreadDispatcher.run { [weak self] in
52
+ rewardedAd.show(fromRootViewController: self?.rewardedAdFactory?.viewController)
53
53
  }
54
54
  } else {
55
55
  BSULogger.warning("Rewarded", message: "cannot show: ad not ready")
package/README.md CHANGED
@@ -15,17 +15,21 @@ This SDK supports four types of Ads:
15
15
 
16
16
  ## Prerequisites
17
17
 
18
- - Use Unity 2020.3 or higher
18
+ - Use Unity 2020.3 or higher (Unity 6 or higher recommended and tested)
19
19
  - Android
20
- - Minimum Android API level of 21 or higher
21
- - Target Android API level 31 or higher
20
+ - Minimum Android API level of 21 or higher
21
+ - Target Android API level 34 or higher
22
+ - JDK 17 or higher
22
23
  - iOS
23
- - Xcode 15.3 or higher
24
- - iOS 12.2 or higher
24
+ - Xcode 15.3 or higher
25
+ - iOS 13.0 or higher
25
26
  - [CocoaPods](https://guides.cocoapods.org/using/getting-started.html)
26
27
 
27
- :::info
28
- Android API level can be set in `Project Settings > Player > Android > Other Settings > Other Settings.`
28
+ :::note
29
+ - Android API level can be set in `Project Settings > Player > Android > Other Settings > Other Settings.`
30
+ - Set your AdMob App ID in `Azerion > BlueStack > Settings` when the Google Mobile Ads adapter is enabled
31
+ - Requires **External Dependency Manager for Unity (EDM4U)**, It resolves all native Android/iOS dependencies
32
+ - AndroidX enabled; enable the Custom Main Gradle Template and Custom Gradle Properties Template, then resolve via `External Dependency Manager > Android Resolver`
29
33
  :::
30
34
 
31
35
  ## Import the package
@@ -1,16 +1,11 @@
1
1
  namespace Azerion.BlueStack.API.Banner
2
2
  {
3
3
  /// <summary>
4
- /// Anchoring mode for a banner ad. Top/Bottom snap the banner to a screen edge
5
- /// (with optional safe-area respect); <see cref="Custom"/> uses an explicit screen-space
6
- /// position supplied via <see cref="BannerAd.SetPosition(UnityEngine.Vector2)"/> or the
7
- /// <c>Vector2</c> / anchor constructor.
4
+ /// Anchoring mode for a sticky banner ad: Top or Bottom snaps the banner to a screen edge
5
+ /// (with optional safe-area respect).
8
6
  /// </summary>
9
7
  public enum AdPosition
10
8
  {
11
- /// <summary>Banner is positioned at an explicit screen-space coordinate. Ignores the safe area.</summary>
12
- Custom = -1,
13
-
14
9
  /// <summary>Banner is anchored to the top of the screen.</summary>
15
10
  Top = 0,
16
11
 
@@ -5,6 +5,6 @@ namespace Azerion.BlueStack.Common
5
5
  {
6
6
  public static class BlueStackSdkInfo
7
7
  {
8
- public const string Version = "6.0.0-beta.3";
8
+ public const string Version = "6.0.0-beta.5";
9
9
  }
10
10
  }
@@ -30,21 +30,30 @@ namespace Azerion.BlueStack.Platforms.Android
30
30
  public void Create(string placementId)
31
31
  {
32
32
  _placementId = placementId;
33
- _unityNativeAd.Call("create", placementId);
33
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
34
+ {
35
+ _unityNativeAd.Call("create", placementId);
36
+ });
34
37
  }
35
38
 
36
39
  public void Load()
37
40
  {
38
41
  BlueStackLogger.Debug("NativeAd", "Load requested for " + _placementId);
39
- _unityNativeAd.Call("load");
42
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
43
+ {
44
+ _unityNativeAd.Call("load");
45
+ });
40
46
  }
41
47
 
42
48
  public void Load(RequestOptions options)
43
49
  {
44
50
  BlueStackLogger.Debug("NativeAd", "Load requested for " + _placementId);
45
- using AndroidJavaObject unityOptions = RequestOptionsClient.CreateUnityRequestOptions(options);
46
- using AndroidJavaObject sdkOptions = unityOptions.Call<AndroidJavaObject>("getRequestOptions");
47
- _unityNativeAd.Call("load", sdkOptions);
51
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
52
+ {
53
+ using AndroidJavaObject unityOptions = RequestOptionsClient.CreateUnityRequestOptions(options);
54
+ using AndroidJavaObject sdkOptions = unityOptions.Call<AndroidJavaObject>("getRequestOptions");
55
+ _unityNativeAd.Call("load", sdkOptions);
56
+ });
48
57
  }
49
58
 
50
59
  public string GetTitle()
@@ -91,18 +100,28 @@ namespace Azerion.BlueStack.Platforms.Android
91
100
 
92
101
  public void RecordImpression()
93
102
  {
94
- _unityNativeAd.Call("recordImpression");
103
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
104
+ {
105
+ _unityNativeAd.Call("recordImpression");
106
+ });
95
107
  }
96
-
108
+
97
109
  public void PerformClick()
98
110
  {
99
- _unityNativeAd.Call("performClick");
111
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
112
+ {
113
+ _unityNativeAd.Call("performClick");
114
+ });
100
115
  }
101
116
 
102
117
  public void Destroy()
103
118
  {
104
119
  BlueStackLogger.Debug("NativeAd", "Destroy requested");
105
- _unityNativeAd.Call("destroy");
120
+ AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
121
+ {
122
+ _unityNativeAd.Call("destroy");
123
+ _unityNativeAd.Dispose();
124
+ });
106
125
  }
107
126
 
108
127
  public void onNativeAdDidLoad()
@@ -130,7 +149,6 @@ namespace Azerion.BlueStack.Platforms.Android
130
149
  public void onNativeAdClose()
131
150
  {
132
151
  OnNativeAdClosed?.Invoke(this, EventArgs.Empty);
133
- _unityNativeAd.Dispose();
134
152
  }
135
153
  }
136
154
  }
@@ -15,6 +15,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
15
15
  {
16
16
  private string _placementId;
17
17
  private AdPosition _adPosition;
18
+ private bool _isCustomPosition;
18
19
  private AdSize _adSize;
19
20
  private Vector2 _customPosition;
20
21
  private Rect _maskBounds;
@@ -54,6 +55,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
54
55
  _placementId = placementId;
55
56
  _adSize = adSize;
56
57
  _adPosition = adPosition;
58
+ _isCustomPosition = false;
57
59
  }
58
60
 
59
61
  public void CreateBannerAd(string placementId, AdSize adSize, Vector2 adPosition)
@@ -61,7 +63,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
61
63
  _placementId = placementId;
62
64
  _adSize = adSize;
63
65
  _customPosition = adPosition;
64
- _adPosition = AdPosition.Custom;
66
+ _isCustomPosition = true;
65
67
  }
66
68
 
67
69
  public void Load()
@@ -169,6 +171,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
169
171
  public void SetPosition(AdPosition adPosition)
170
172
  {
171
173
  _adPosition = adPosition;
174
+ _isCustomPosition = false;
172
175
  if (dummyAd == null)
173
176
  {
174
177
  return;
@@ -179,7 +182,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
179
182
  public void SetPosition(Vector2 adPosition)
180
183
  {
181
184
  _customPosition = adPosition;
182
- _adPosition = AdPosition.Custom;
185
+ _isCustomPosition = true;
183
186
  if (dummyAd == null)
184
187
  {
185
188
  return;
@@ -219,7 +222,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
219
222
  private void PositionBannerContainer()
220
223
  {
221
224
  if (_bannerContainer == null) return;
222
- if (_adPosition == AdPosition.Custom)
225
+ if (_isCustomPosition)
223
226
  {
224
227
  AnchorBannerContainerToScreenPoint(_customPosition);
225
228
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "com.azerion.bluestack",
3
- "version": "6.0.0-beta.3",
3
+ "version": "6.0.0-beta.5",
4
4
  "displayName": "BlueStack",
5
5
  "description": "BlueStack SDK has been designed to give developers options for showing Ads from different ad networks.",
6
6
  "unity": "2020.3",
7
7
  "documentationUrl": "https://developers.bluestack.app/unity",
8
- "changelogUrl": "https://developers.bluestack.app/unity/changelog",
8
+ "changelogUrl": "https://developers.bluestack.app/unity/release-notes",
9
9
  "dependencies": {
10
10
  "com.google.external-dependency-manager": "1.2.187"
11
11
  },