react-native-daro 1.0.15 → 1.0.16

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.
Files changed (192) hide show
  1. package/android/build.gradle +11 -1
  2. package/android/src/main/java/com/daro/reactnative/DaroMAdBannerViewManager.kt +34 -11
  3. package/android/src/main/java/com/daro/reactnative/DaroMAdNativeViewManager.kt +79 -48
  4. package/android/src/main/java/com/daro/reactnative/DaroMModule.kt +80 -25
  5. package/android/src/main/java/com/daro/reactnative/DaroMPackage.kt +28 -4
  6. package/android/src/main/java/com/daro/reactnative/DaroMediaViewManager.kt +25 -0
  7. package/android/src/main/java/com/daro/reactnative/event/DaroMEvent.kt +18 -7
  8. package/android/src/main/java/com/daro/reactnative/util/EventEmitterUtil.kt +18 -5
  9. package/android/src/main/java/com/daro/reactnative/view/DaroMAdNativeViewContainer.kt +301 -52
  10. package/android/src/main/java/com/daro/reactnative/view/DaroMediaView.kt +123 -0
  11. package/android/src/newarch/java/com/daro/reactnative/DaroMModuleSpec.kt +6 -0
  12. package/android/src/oldarch/java/com/daro/reactnative/DaroMModuleSpec.kt +90 -0
  13. package/ios/DaroMModule+AppOpen.swift +13 -10
  14. package/ios/DaroMModule+Interstitial.swift +12 -9
  15. package/ios/DaroMModule+LightPopup.swift +16 -13
  16. package/ios/DaroMModule+Rewarded.swift +12 -9
  17. package/ios/DaroMModule.mm +45 -17
  18. package/ios/DaroMModule.swift +4 -4
  19. package/ios/Fabric/DaroMAdBannerComponentView.mm +207 -0
  20. package/ios/Fabric/DaroMFabricEventSink.mm +31 -0
  21. package/ios/Fabric/DaroMFabricPropApplier.mm +123 -0
  22. package/ios/Fabric/DaroMNativeAdComponentView.mm +294 -0
  23. package/ios/{DaroMAdBannerViewManager.m → Paper/DaroMAdBannerViewManager.m} +3 -1
  24. package/ios/Paper/DaroMAdBannerViewManager.swift +21 -0
  25. package/ios/{DaroMNativeAdViewManager.m → Paper/DaroMNativeAdViewManager.m} +2 -1
  26. package/ios/Paper/DaroMNativeAdViewManager.swift +22 -0
  27. package/ios/Shared/DaroMAdEventSink.swift +24 -0
  28. package/ios/Shared/DaroMBannerAdView.swift +188 -0
  29. package/ios/Shared/DaroMNativeAdLoadCoordinator.swift +49 -0
  30. package/ios/Shared/DaroMNativeAdView.swift +575 -0
  31. package/ios/Shared/DaroMNativeAssetResolver.swift +53 -0
  32. package/ios/mediation/admob/DaroMediationTypes.swift +1 -0
  33. package/lib/commonjs/AdBannerView.js +20 -39
  34. package/lib/commonjs/AdBannerView.js.map +1 -1
  35. package/lib/commonjs/AppOpenAd.js +43 -10
  36. package/lib/commonjs/AppOpenAd.js.map +1 -1
  37. package/lib/commonjs/DaroMAdBannerViewNativeComponent.ts +47 -0
  38. package/lib/commonjs/DaroMNativeAdViewNativeComponent.ts +62 -0
  39. package/lib/commonjs/DaroMediaViewNativeComponent.ts +9 -0
  40. package/lib/commonjs/EventEmitter.js +4 -5
  41. package/lib/commonjs/EventEmitter.js.map +1 -1
  42. package/lib/commonjs/InterstitialAd.js +9 -11
  43. package/lib/commonjs/InterstitialAd.js.map +1 -1
  44. package/lib/commonjs/LightPopupAd.js +11 -12
  45. package/lib/commonjs/LightPopupAd.js.map +1 -1
  46. package/lib/commonjs/NativeDaroModule.js +9 -0
  47. package/lib/commonjs/NativeDaroModule.js.map +1 -0
  48. package/lib/commonjs/RewardedAd.js +9 -11
  49. package/lib/commonjs/RewardedAd.js.map +1 -1
  50. package/lib/commonjs/codegen-env.d.js +2 -0
  51. package/lib/commonjs/codegen-env.d.js.map +1 -0
  52. package/lib/commonjs/hooks/useInterstitialAd.js +110 -0
  53. package/lib/commonjs/hooks/useInterstitialAd.js.map +1 -0
  54. package/lib/commonjs/hooks/useLightPopupAd.js +114 -0
  55. package/lib/commonjs/hooks/useLightPopupAd.js.map +1 -0
  56. package/lib/commonjs/hooks/useRewardedAd.js +113 -0
  57. package/lib/commonjs/hooks/useRewardedAd.js.map +1 -0
  58. package/lib/commonjs/index.js +29 -13
  59. package/lib/commonjs/index.js.map +1 -1
  60. package/lib/commonjs/nativeAd/NativeAdView.js +23 -22
  61. package/lib/commonjs/nativeAd/NativeAdView.js.map +1 -1
  62. package/lib/commonjs/nativeAd/NativeAdViewComponents.js +43 -31
  63. package/lib/commonjs/nativeAd/NativeAdViewComponents.js.map +1 -1
  64. package/lib/commonjs/nativeAd/NativeAdViewProvider.js +34 -7
  65. package/lib/commonjs/nativeAd/NativeAdViewProvider.js.map +1 -1
  66. package/lib/module/AdBannerView.js +19 -40
  67. package/lib/module/AdBannerView.js.map +1 -1
  68. package/lib/module/AppOpenAd.js +43 -11
  69. package/lib/module/AppOpenAd.js.map +1 -1
  70. package/lib/module/DaroMAdBannerViewNativeComponent.ts +47 -0
  71. package/lib/module/DaroMNativeAdViewNativeComponent.ts +62 -0
  72. package/lib/module/DaroMediaViewNativeComponent.ts +9 -0
  73. package/lib/module/EventEmitter.js +4 -6
  74. package/lib/module/EventEmitter.js.map +1 -1
  75. package/lib/module/InterstitialAd.js +8 -11
  76. package/lib/module/InterstitialAd.js.map +1 -1
  77. package/lib/module/LightPopupAd.js +11 -13
  78. package/lib/module/LightPopupAd.js.map +1 -1
  79. package/lib/module/NativeDaroModule.js +5 -0
  80. package/lib/module/NativeDaroModule.js.map +1 -0
  81. package/lib/module/RewardedAd.js +8 -11
  82. package/lib/module/RewardedAd.js.map +1 -1
  83. package/lib/module/codegen-env.d.js +2 -0
  84. package/lib/module/codegen-env.d.js.map +1 -0
  85. package/lib/module/hooks/useInterstitialAd.js +106 -0
  86. package/lib/module/hooks/useInterstitialAd.js.map +1 -0
  87. package/lib/module/hooks/useLightPopupAd.js +110 -0
  88. package/lib/module/hooks/useLightPopupAd.js.map +1 -0
  89. package/lib/module/hooks/useRewardedAd.js +109 -0
  90. package/lib/module/hooks/useRewardedAd.js.map +1 -0
  91. package/lib/module/index.js +7 -13
  92. package/lib/module/index.js.map +1 -1
  93. package/lib/module/nativeAd/NativeAdView.js +22 -23
  94. package/lib/module/nativeAd/NativeAdView.js.map +1 -1
  95. package/lib/module/nativeAd/NativeAdViewComponents.js +45 -34
  96. package/lib/module/nativeAd/NativeAdViewComponents.js.map +1 -1
  97. package/lib/module/nativeAd/NativeAdViewProvider.js +35 -8
  98. package/lib/module/nativeAd/NativeAdViewProvider.js.map +1 -1
  99. package/lib/typescript/commonjs/plugin/src/index.d.ts +1 -1
  100. package/lib/typescript/commonjs/plugin/src/index.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/plugin/src/withAndroid.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/plugin/src/withIos.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/src/AdBannerView.d.ts +2 -2
  104. package/lib/typescript/commonjs/src/AdBannerView.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/src/AppOpenAd.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/src/DaroMAdBannerViewNativeComponent.d.ts +32 -0
  107. package/lib/typescript/commonjs/src/DaroMAdBannerViewNativeComponent.d.ts.map +1 -0
  108. package/lib/typescript/commonjs/src/DaroMNativeAdViewNativeComponent.d.ts +42 -0
  109. package/lib/typescript/commonjs/src/DaroMNativeAdViewNativeComponent.d.ts.map +1 -0
  110. package/lib/typescript/commonjs/src/DaroMediaViewNativeComponent.d.ts +6 -0
  111. package/lib/typescript/commonjs/src/DaroMediaViewNativeComponent.d.ts.map +1 -0
  112. package/lib/typescript/commonjs/src/EventEmitter.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/src/InterstitialAd.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/src/LightPopupAd.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/src/NativeDaroModule.d.ts +61 -0
  116. package/lib/typescript/commonjs/src/NativeDaroModule.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/src/RewardedAd.d.ts.map +1 -1
  118. package/lib/typescript/commonjs/src/hooks/useInterstitialAd.d.ts +19 -0
  119. package/lib/typescript/commonjs/src/hooks/useInterstitialAd.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/src/hooks/useLightPopupAd.d.ts +23 -0
  121. package/lib/typescript/commonjs/src/hooks/useLightPopupAd.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/src/hooks/useRewardedAd.d.ts +20 -0
  123. package/lib/typescript/commonjs/src/hooks/useRewardedAd.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/src/index.d.ts +7 -1
  125. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/src/nativeAd/NativeAdView.d.ts +1 -0
  127. package/lib/typescript/commonjs/src/nativeAd/NativeAdView.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/src/nativeAd/NativeAdViewComponents.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/src/nativeAd/NativeAdViewProvider.d.ts +11 -5
  130. package/lib/typescript/commonjs/src/nativeAd/NativeAdViewProvider.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/src/types/NativeAdViewProps.d.ts +2 -0
  132. package/lib/typescript/commonjs/src/types/NativeAdViewProps.d.ts.map +1 -1
  133. package/lib/typescript/module/plugin/src/index.d.ts +1 -1
  134. package/lib/typescript/module/plugin/src/index.d.ts.map +1 -1
  135. package/lib/typescript/module/plugin/src/withAndroid.d.ts.map +1 -1
  136. package/lib/typescript/module/plugin/src/withIos.d.ts.map +1 -1
  137. package/lib/typescript/module/src/AdBannerView.d.ts +2 -2
  138. package/lib/typescript/module/src/AdBannerView.d.ts.map +1 -1
  139. package/lib/typescript/module/src/AppOpenAd.d.ts.map +1 -1
  140. package/lib/typescript/module/src/DaroMAdBannerViewNativeComponent.d.ts +32 -0
  141. package/lib/typescript/module/src/DaroMAdBannerViewNativeComponent.d.ts.map +1 -0
  142. package/lib/typescript/module/src/DaroMNativeAdViewNativeComponent.d.ts +42 -0
  143. package/lib/typescript/module/src/DaroMNativeAdViewNativeComponent.d.ts.map +1 -0
  144. package/lib/typescript/module/src/DaroMediaViewNativeComponent.d.ts +6 -0
  145. package/lib/typescript/module/src/DaroMediaViewNativeComponent.d.ts.map +1 -0
  146. package/lib/typescript/module/src/EventEmitter.d.ts.map +1 -1
  147. package/lib/typescript/module/src/InterstitialAd.d.ts.map +1 -1
  148. package/lib/typescript/module/src/LightPopupAd.d.ts.map +1 -1
  149. package/lib/typescript/module/src/NativeDaroModule.d.ts +61 -0
  150. package/lib/typescript/module/src/NativeDaroModule.d.ts.map +1 -0
  151. package/lib/typescript/module/src/RewardedAd.d.ts.map +1 -1
  152. package/lib/typescript/module/src/hooks/useInterstitialAd.d.ts +19 -0
  153. package/lib/typescript/module/src/hooks/useInterstitialAd.d.ts.map +1 -0
  154. package/lib/typescript/module/src/hooks/useLightPopupAd.d.ts +23 -0
  155. package/lib/typescript/module/src/hooks/useLightPopupAd.d.ts.map +1 -0
  156. package/lib/typescript/module/src/hooks/useRewardedAd.d.ts +20 -0
  157. package/lib/typescript/module/src/hooks/useRewardedAd.d.ts.map +1 -0
  158. package/lib/typescript/module/src/index.d.ts +7 -1
  159. package/lib/typescript/module/src/index.d.ts.map +1 -1
  160. package/lib/typescript/module/src/nativeAd/NativeAdView.d.ts +1 -0
  161. package/lib/typescript/module/src/nativeAd/NativeAdView.d.ts.map +1 -1
  162. package/lib/typescript/module/src/nativeAd/NativeAdViewComponents.d.ts.map +1 -1
  163. package/lib/typescript/module/src/nativeAd/NativeAdViewProvider.d.ts +11 -5
  164. package/lib/typescript/module/src/nativeAd/NativeAdViewProvider.d.ts.map +1 -1
  165. package/lib/typescript/module/src/types/NativeAdViewProps.d.ts +2 -0
  166. package/lib/typescript/module/src/types/NativeAdViewProps.d.ts.map +1 -1
  167. package/package.json +12 -5
  168. package/plugin/build/index.d.ts +1 -1
  169. package/plugin/build/withAndroid.js +99 -17
  170. package/plugin/build/withIos.js +4 -5
  171. package/react-native-daro.podspec +3 -0
  172. package/src/AdBannerView.tsx +24 -61
  173. package/src/AppOpenAd.ts +46 -9
  174. package/src/DaroMAdBannerViewNativeComponent.ts +47 -0
  175. package/src/DaroMNativeAdViewNativeComponent.ts +62 -0
  176. package/src/DaroMediaViewNativeComponent.ts +9 -0
  177. package/src/EventEmitter.ts +4 -5
  178. package/src/InterstitialAd.ts +8 -10
  179. package/src/LightPopupAd.ts +11 -12
  180. package/src/NativeDaroModule.ts +91 -0
  181. package/src/RewardedAd.ts +8 -10
  182. package/src/codegen-env.d.ts +45 -0
  183. package/src/hooks/useInterstitialAd.ts +117 -0
  184. package/src/hooks/useLightPopupAd.ts +132 -0
  185. package/src/hooks/useRewardedAd.ts +122 -0
  186. package/src/index.tsx +21 -22
  187. package/src/nativeAd/NativeAdView.tsx +41 -54
  188. package/src/nativeAd/NativeAdViewComponents.tsx +80 -42
  189. package/src/nativeAd/NativeAdViewProvider.tsx +75 -17
  190. package/src/types/NativeAdViewProps.ts +7 -0
  191. package/ios/DaroMAdBannerViewManager.swift +0 -91
  192. package/ios/DaroMNativeAdViewManager.swift +0 -291
@@ -1,11 +1,13 @@
1
1
  package com.daro.reactnative.util
2
2
 
3
3
  import android.view.View
4
+ import com.daro.reactnative.event.AdViewEvent
4
5
  import com.daro.reactnative.event.DaroMEvent
5
6
  import com.facebook.react.bridge.ReactContext
6
7
  import com.facebook.react.bridge.WritableMap
7
8
  import com.facebook.react.modules.core.DeviceEventManagerModule
8
- import com.facebook.react.uimanager.events.RCTEventEmitter
9
+ import com.facebook.react.uimanager.UIManagerHelper
10
+ import com.facebook.react.uimanager.events.Event
9
11
 
10
12
  internal object EventEmitterUtil {
11
13
  // React Native Bridge
@@ -15,10 +17,21 @@ internal object EventEmitterUtil {
15
17
  .emit(event.value, params)
16
18
  }
17
19
 
18
- fun View.sendNativeViewEvent(reactContext: ReactContext, event: DaroMEvent, params: WritableMap?) {
19
- reactContext
20
- .getJSModule(RCTEventEmitter::class.java)
21
- .receiveEvent(id, event.value, params)
20
+ fun View.sendNativeViewEvent(reactContext: ReactContext, event: AdViewEvent, params: WritableMap?) {
21
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) ?: return
22
+ eventDispatcher.dispatchEvent(
23
+ AdViewDispatchedEvent(UIManagerHelper.getSurfaceId(reactContext), id, event, params)
24
+ )
22
25
  }
23
26
  }
24
27
 
28
+ private class AdViewDispatchedEvent(
29
+ surfaceId: Int,
30
+ viewId: Int,
31
+ private val event: AdViewEvent,
32
+ private val payload: WritableMap?,
33
+ ) : Event<AdViewDispatchedEvent>(surfaceId, viewId) {
34
+ override fun getEventName() = event.value
35
+
36
+ override fun getEventData() = payload
37
+ }
@@ -7,7 +7,9 @@ import android.text.SpannableString
7
7
  import android.text.Spanned
8
8
  import android.view.View
9
9
  import android.view.ViewGroup
10
+ import android.widget.FrameLayout
10
11
  import android.widget.ImageView
12
+ import android.widget.TextView
11
13
  import androidx.annotation.IdRes
12
14
  import androidx.core.view.children
13
15
  import com.daro.reactnative.event.AdViewEvent
@@ -20,6 +22,7 @@ import droom.daro.core.adunit.DaroNativeAdUnit
20
22
  import droom.daro.core.model.DaroAdInfo
21
23
  import droom.daro.core.model.DaroAdLoadError
22
24
  import droom.daro.core.model.DaroNativeAdBinder
25
+ import droom.daro.core.model.DaroNativeAdChoicePlacement
23
26
  import droom.daro.core.model.DaroViewAd
24
27
  import droom.daro.view.DaroAdViewListener
25
28
  import droom.daro.view.DaroNativeAdView
@@ -31,6 +34,11 @@ import kotlin.math.roundToInt
31
34
  class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(context),
32
35
  DaroAdViewListener {
33
36
 
37
+ private companion object {
38
+ private const val MAX_LOAD_ASSET_VIEW_RETRY_COUNT = 40
39
+ private const val LOAD_ASSET_VIEW_RETRY_DELAY_MS = 50L
40
+ }
41
+
34
42
  private val adViewContainer: ReactViewGroup = ReactViewGroup(context)
35
43
 
36
44
  init {
@@ -46,24 +54,75 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
46
54
  internal var adUnitId: String = ""
47
55
 
48
56
  internal var loadOnMount: Boolean = true
57
+ private var adChoicesPosition: DaroNativeAdChoicePlacement =
58
+ DaroNativeAdChoicePlacement.BOTTOM_RIGHT
49
59
 
50
60
  private var isFirstLoad: Boolean = true
61
+ private var isDestroyed: Boolean = false
62
+ private var pendingLoadAssetViewRetryCount: Int = 0
51
63
 
52
- fun loadAd() {
53
- (adView ?: buildAdView())?.apply {
64
+ fun addReactSubview(child: View, index: Int) {
65
+ if (isDestroyed) return
54
66
 
55
- adView = this
67
+ if (index < 0 || index >= adViewContainer.childCount) {
68
+ adViewContainer.addView(child)
69
+ } else {
70
+ adViewContainer.addView(child, index)
71
+ }
56
72
 
57
- if (this.parent == null) {
58
- this@DaroMAdNativeViewContainer.addView(this)
59
- }
73
+ scheduleInitialLoadIfNeeded()
74
+ }
60
75
 
61
- loadAd()
62
- }
76
+ fun getReactSubviewCount(): Int {
77
+ if (isDestroyed) return 0
78
+
79
+ return adViewContainer.childCount
80
+ }
81
+
82
+ fun getReactSubviewAt(index: Int): View {
83
+ return adViewContainer.getChildAt(index)
84
+ }
85
+
86
+ fun removeReactSubviewAt(index: Int) {
87
+ if (isDestroyed || index < 0 || index >= adViewContainer.childCount) return
88
+
89
+ adViewContainer.removeViewAt(index)
90
+ }
91
+
92
+ fun removeAllReactSubviews() {
93
+ if (isDestroyed) return
94
+
95
+ adViewContainer.removeAllViews()
63
96
  }
64
97
 
65
- internal fun applyPrevSetting() {
66
- if(adView!= null && adView!!.parent == null) addView(adView)
98
+ fun loadAd(): Boolean {
99
+ if (isDestroyed) return false
100
+
101
+ if (!hasRegisteredAssetView()) {
102
+ scheduleLoadWhenAssetViewsMounted()
103
+ return false
104
+ }
105
+
106
+ if (!areRegisteredAssetViewsMounted()) {
107
+ scheduleLoadWhenAssetViewsMounted()
108
+ return false
109
+ }
110
+
111
+ pendingLoadAssetViewRetryCount = 0
112
+ captureRegisteredTextSpans()
113
+
114
+ return (adView ?: buildAdView())?.let { view ->
115
+
116
+ adView = view
117
+
118
+ if (view.parent == null) {
119
+ this@DaroMAdNativeViewContainer.addView(view)
120
+ }
121
+
122
+ view.loadAd()
123
+ isFirstLoad = false
124
+ true
125
+ } ?: false
67
126
  }
68
127
 
69
128
  private fun buildAdView(): DaroNativeAdView? {
@@ -84,8 +143,13 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
84
143
  .let { if (titleViewId != null) it.setTitleViewId(titleViewId!!) else it }
85
144
  .let { if (bodyViewId != null) it.setBodyTextViewId(bodyViewId!!) else it }
86
145
  .let { if (iconViewId != null) it.setIconViewId(iconViewId!!) else it }
87
- .let { if (mediaViewId != null) it.setMediaViewGroupId(mediaViewId!!) else it }
146
+ .let { builder ->
147
+ // 등록 누락 시 더미 뷰에 바인딩되지 않도록 DaroMediaView를 직접 찾아 폴백한다.
148
+ val mediaId = mediaViewId ?: findDaroMediaViewId()
149
+ if (mediaId != null) builder.setMediaViewGroupId(mediaId) else builder
150
+ }
88
151
  .let { if (callToActionViewId != null) it.setCallToActionViewId(callToActionViewId!!) else it }
152
+ .setAdChoicePlacement(adChoicesPosition)
89
153
  .build()
90
154
  )
91
155
 
@@ -109,13 +173,34 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
109
173
  }
110
174
 
111
175
  fun destroy() {
112
- adView?.destroy()
176
+ isDestroyed = true
177
+ loadAdHandler.removeCallbacksAndMessages(null)
178
+ setOnClickListener(null)
179
+
180
+ adView?.let { view ->
181
+ (view.parent as? ViewGroup)?.removeView(view)
182
+ view.destroy()
183
+ installFallbackAssetViews()
184
+ }
185
+ adView = null
186
+ pendingLoadAssetViewRetryCount = 0
187
+ removeAllViews()
188
+ testSpanMap.clear()
189
+ }
190
+
191
+ override fun onAttachedToWindow() {
192
+ super.onAttachedToWindow()
193
+ if (!isDestroyed) {
194
+ adView?.resume()
195
+ }
113
196
  }
114
197
 
115
198
  override fun onAdLoadSuccess(
116
199
  ad: DaroViewAd,
117
200
  adInfo: DaroAdInfo,
118
201
  ) {
202
+ if (isDestroyed) return
203
+
119
204
  sendNativeViewEvent(
120
205
  reactContext = context,
121
206
  event = AdViewEvent.ON_AD_LOADED,
@@ -123,6 +208,7 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
123
208
  )
124
209
 
125
210
  post({
211
+ layoutMediaViewChildrenFallback()
126
212
  context.currentActivity?.window.let { window ->
127
213
  window?.decorView?.requestLayout()
128
214
  }
@@ -130,6 +216,8 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
130
216
  }
131
217
 
132
218
  override fun onAdLoadFail(err: DaroAdLoadError) {
219
+ if (isDestroyed) return
220
+
133
221
  sendNativeViewEvent(
134
222
  reactContext = context,
135
223
  event = AdViewEvent.ON_AD_FAILED_TO_LOAD,
@@ -138,6 +226,8 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
138
226
  }
139
227
 
140
228
  override fun onAdClicked(adInfo: DaroAdInfo) {
229
+ if (isDestroyed) return
230
+
141
231
  sendNativeViewEvent(
142
232
  reactContext = context,
143
233
  event = AdViewEvent.ON_AD_CLICKED,
@@ -146,6 +236,8 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
146
236
  }
147
237
 
148
238
  override fun onAdImpression(adInfo: DaroAdInfo) {
239
+ if (isDestroyed) return
240
+
149
241
  sendNativeViewEvent(
150
242
  reactContext = context,
151
243
  event = AdViewEvent.ON_AD_IMPRESSION,
@@ -155,19 +247,30 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
155
247
 
156
248
  private val loadAdHandler = Handler(Looper.getMainLooper())
157
249
  private val loadAdTask = LoadAdTask(this)
250
+ private val retryLoadAdTask = Runnable {
251
+ if (!isDestroyed) {
252
+ loadAd()
253
+ }
254
+ }
158
255
 
159
256
  private inner class LoadAdTask(private val nativeAdView: DaroMAdNativeViewContainer) :
160
257
  Runnable {
161
258
 
162
259
  override fun run() {
163
260
  nativeAdView.loadAd()
164
- nativeAdView.isFirstLoad = false
165
261
  }
166
262
  }
167
263
 
168
264
  internal fun onSetProps() {
169
- if (loadOnMount && isFirstLoad) {
170
- loadAdHandler.removeCallbacksAndMessages(null)
265
+ if (isDestroyed) return
266
+
267
+ scheduleInitialLoadIfNeeded()
268
+ }
269
+
270
+ private fun scheduleInitialLoadIfNeeded() {
271
+ loadAdHandler.removeCallbacks(loadAdTask)
272
+
273
+ if (loadOnMount && isFirstLoad && adUnitId.isNotEmpty() && hasRegisteredAssetView()) {
171
274
  loadAdHandler.postDelayed(loadAdTask, 50)
172
275
  }
173
276
  }
@@ -185,7 +288,11 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
185
288
  )
186
289
 
187
290
  adView?.layout(0, 0, width, height)
291
+ } catch (_: Exception) {
292
+ // Ignore
293
+ }
188
294
 
295
+ try {
189
296
  adViewContainer.parent?.let {
190
297
  (it as ViewGroup).children.filter { it != adViewContainer && it is ViewGroup }.lastOrNull()
191
298
  ?.let {
@@ -199,8 +306,10 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
199
306
  findImageView(it as ViewGroup)?.let { adChoiceImageView ->
200
307
  if ((adChoiceImageView.parent as View).measuredWidth == adChoiceImageView.measuredWidth) return@let
201
308
 
309
+ val adChoiceDrawable = adChoiceImageView.drawable ?: return@let
310
+
202
311
  val ratio =
203
- adChoiceImageView.drawable.intrinsicHeight.toFloat() / (adChoiceImageView.parent as View).height
312
+ adChoiceDrawable.intrinsicHeight.toFloat() / (adChoiceImageView.parent as View).height
204
313
 
205
314
  val w = ((adChoiceImageView.parent as View).width * ratio).roundToInt()
206
315
  val h = ((adChoiceImageView.parent as View).height * ratio).roundToInt()
@@ -223,27 +332,18 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
223
332
  }
224
333
  }
225
334
  }
335
+ } catch (_: Exception) {
336
+ // Ignore
337
+ }
226
338
 
227
- mediaViewId?.let { adViewContainer.findViewById<View>(it) }?.let {
228
- (it as ViewGroup).children.forEach { child ->
229
- val width = it.width
230
- val height = it.height
231
-
232
- child.measure(
233
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
234
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
235
- )
236
-
237
- child.layout(0, 0, width, height)
238
- }
239
- }
240
-
241
- titleViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }
242
- ?.restoreSpannableFromMap()
243
- bodyViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }?.restoreSpannableFromMap()
244
- callToActionViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }
245
- ?.restoreSpannableFromMap()
339
+ try {
340
+ layoutMediaViewChildrenFallback()
341
+ } catch (_: Exception) {
342
+ // Ignore
343
+ }
246
344
 
345
+ try {
346
+ restoreRegisteredTextSpans()
247
347
  } catch (_: Exception) {
248
348
  // Ignore
249
349
  }
@@ -262,6 +362,99 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
262
362
  return null
263
363
  }
264
364
 
365
+ // 미디어 그룹이 DaroMediaView가 아닌 경우(구버전 JS 조합)용 직계 자식 폴백.
366
+ private fun layoutMediaViewChildrenFallback() {
367
+ val mediaViewGroup = getMediaViewGroup() ?: return
368
+ if (mediaViewGroup is DaroMediaView) return
369
+
370
+ val width = mediaViewGroup.width
371
+ val height = mediaViewGroup.height
372
+
373
+ if (width <= 0 || height <= 0) return
374
+
375
+ mediaViewGroup.children.forEach { child ->
376
+ child.measure(
377
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
378
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
379
+ )
380
+
381
+ child.layout(0, 0, width, height)
382
+ }
383
+ }
384
+
385
+ private fun findDaroMediaViewId(): Int? {
386
+ return findDaroMediaView(adViewContainer)?.id?.takeIf { it != View.NO_ID }
387
+ }
388
+
389
+ private fun findDaroMediaView(root: ViewGroup): DaroMediaView? {
390
+ root.children.forEach { child ->
391
+ if (child is DaroMediaView) return child
392
+ if (child is ViewGroup) findDaroMediaView(child)?.let { return it }
393
+ }
394
+ return null
395
+ }
396
+
397
+ private fun getMediaViewGroup(): ViewGroup? {
398
+ return mediaViewId?.let { findRegisteredAssetView(it) as? ViewGroup }
399
+ }
400
+
401
+ private fun hasRegisteredAssetView(): Boolean {
402
+ return titleViewId != null ||
403
+ bodyViewId != null ||
404
+ callToActionViewId != null ||
405
+ iconViewId != null ||
406
+ mediaViewId != null
407
+ }
408
+
409
+ private fun areRegisteredAssetViewsMounted(): Boolean {
410
+ if (!hasRegisteredAssetView()) return false
411
+
412
+ titleViewId?.let {
413
+ if (findRegisteredAssetView(it) !is TextView) return false
414
+ }
415
+
416
+ bodyViewId?.let {
417
+ if (findRegisteredAssetView(it) !is TextView) return false
418
+ }
419
+
420
+ callToActionViewId?.let {
421
+ if (findRegisteredAssetView(it) !is TextView) return false
422
+ }
423
+
424
+ iconViewId?.let {
425
+ if (findRegisteredAssetView(it) !is ImageView) return false
426
+ }
427
+
428
+ mediaViewId?.let {
429
+ if (findRegisteredAssetView(it) !is ViewGroup) return false
430
+ }
431
+
432
+ return true
433
+ }
434
+
435
+ private fun findRegisteredAssetView(@IdRes id: Int): View? {
436
+ return adViewContainer.findViewById(id)
437
+ }
438
+
439
+ private fun scheduleLoadWhenAssetViewsMounted() {
440
+ if (pendingLoadAssetViewRetryCount >= MAX_LOAD_ASSET_VIEW_RETRY_COUNT) {
441
+ pendingLoadAssetViewRetryCount = 0
442
+ return
443
+ }
444
+
445
+ pendingLoadAssetViewRetryCount += 1
446
+ loadAdHandler.removeCallbacks(retryLoadAdTask)
447
+ loadAdHandler.postDelayed(retryLoadAdTask, LOAD_ASSET_VIEW_RETRY_DELAY_MS)
448
+ }
449
+
450
+ private fun cancelPendingLoadIfNoRegisteredAssetView() {
451
+ if (hasRegisteredAssetView()) return
452
+
453
+ pendingLoadAssetViewRetryCount = 0
454
+ loadAdHandler.removeCallbacks(loadAdTask)
455
+ loadAdHandler.removeCallbacks(retryLoadAdTask)
456
+ }
457
+
265
458
  /*
266
459
  * Mapping View
267
460
  * */
@@ -282,62 +475,118 @@ class DaroMAdNativeViewContainer(val context: ReactContext) : ReactViewGroup(con
282
475
  private var mediaViewId: Int? = null
283
476
 
284
477
  fun setTitleView(tag: Int) {
285
- if (titleViewId != null) return
478
+ if (tag == View.NO_ID || tag == 0) {
479
+ titleViewId = null
480
+ cancelPendingLoadIfNoRegisteredAssetView()
481
+ return
482
+ }
483
+
484
+ if (titleViewId == tag) return
286
485
 
287
486
  this.titleViewId = tag
288
- findViewById<ReactTextView>(tag)?.let {
289
- it.moveViewsToAdViewContainer()
487
+ adViewContainer.findViewById<ReactTextView>(tag)?.let {
290
488
  it.saveSpannableToMap()
291
489
  }
490
+ scheduleInitialLoadIfNeeded()
292
491
  }
293
492
 
294
493
  fun setBodyView(tag: Int) {
295
- if (bodyViewId != null) return
494
+ if (tag == View.NO_ID || tag == 0) {
495
+ bodyViewId = null
496
+ cancelPendingLoadIfNoRegisteredAssetView()
497
+ return
498
+ }
499
+
500
+ if (bodyViewId == tag) return
296
501
 
297
502
  this.bodyViewId = tag
298
- findViewById<ReactTextView>(tag)?.let {
299
- it.moveViewsToAdViewContainer()
503
+ adViewContainer.findViewById<ReactTextView>(tag)?.let {
300
504
  it.saveSpannableToMap()
301
505
  }
506
+ scheduleInitialLoadIfNeeded()
302
507
  }
303
508
 
304
509
  fun setCallToActionView(tag: Int) {
305
- if (callToActionViewId != null) return
510
+ if (tag == View.NO_ID || tag == 0) {
511
+ callToActionViewId = null
512
+ cancelPendingLoadIfNoRegisteredAssetView()
513
+ return
514
+ }
515
+
516
+ if (callToActionViewId == tag) return
306
517
 
307
518
  this.callToActionViewId = tag
308
- findViewById<ReactTextView>(tag)?.let {
309
- it.moveViewsToAdViewContainer()
519
+ adViewContainer.findViewById<ReactTextView>(tag)?.let {
310
520
  it.saveSpannableToMap()
311
521
  }
522
+ scheduleInitialLoadIfNeeded()
312
523
  }
313
524
 
314
525
  fun setIconView(tag: Int) {
315
- if (iconViewId != null) return
526
+ if (tag == View.NO_ID || tag == 0) {
527
+ iconViewId = null
528
+ cancelPendingLoadIfNoRegisteredAssetView()
529
+ return
530
+ }
531
+
532
+ if (iconViewId == tag) return
316
533
 
317
534
  this.iconViewId = tag
318
- findViewById<View>(tag)?.moveViewsToAdViewContainer()
535
+ scheduleInitialLoadIfNeeded()
319
536
  }
320
537
 
321
538
  fun setMediaView(tag: Int) {
322
- if (mediaViewId != null) return
539
+ if (tag == View.NO_ID || tag == 0) {
540
+ mediaViewId = null
541
+ cancelPendingLoadIfNoRegisteredAssetView()
542
+ return
543
+ }
544
+
545
+ if (mediaViewId == tag) return
323
546
 
324
547
  this.mediaViewId = tag
325
- findViewById<View>(tag)?.moveViewsToAdViewContainer()
548
+ scheduleInitialLoadIfNeeded()
549
+ }
550
+
551
+ fun setAdChoicesPosition(value: String?) {
552
+ adChoicesPosition = when (value) {
553
+ "topLeft" -> DaroNativeAdChoicePlacement.TOP_LEFT
554
+ "topRight" -> DaroNativeAdChoicePlacement.TOP_RIGHT
555
+ "bottomLeft" -> DaroNativeAdChoicePlacement.BOTTOM_LEFT
556
+ else -> DaroNativeAdChoicePlacement.BOTTOM_RIGHT
557
+ }
326
558
  }
327
559
 
328
560
  /*
329
561
  * Setting View
330
562
  * */
331
563
 
332
- private fun View.moveViewsToAdViewContainer() {
333
- val layoutParams = layoutParams
564
+ private fun installFallbackAssetViews() {
565
+ adViewContainer.removeAllViews()
334
566
 
335
- (this.parent as? ViewGroup)?.removeView(this)
336
- adViewContainer.addView(this, layoutParams)
567
+ titleViewId?.let { adViewContainer.addView(TextView(context).apply { id = it }) }
568
+ bodyViewId?.let { adViewContainer.addView(TextView(context).apply { id = it }) }
569
+ callToActionViewId?.let { adViewContainer.addView(TextView(context).apply { id = it }) }
570
+ iconViewId?.let { adViewContainer.addView(ImageView(context).apply { id = it }) }
571
+ mediaViewId?.let { adViewContainer.addView(FrameLayout(context).apply { id = it }) }
337
572
  }
338
573
 
339
574
  private val testSpanMap: MutableMap<Int, Spanned> = mutableMapOf()
340
575
 
576
+ private fun captureRegisteredTextSpans() {
577
+ titleViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }?.saveSpannableToMap()
578
+ bodyViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }?.saveSpannableToMap()
579
+ callToActionViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }
580
+ ?.saveSpannableToMap()
581
+ }
582
+
583
+ private fun restoreRegisteredTextSpans() {
584
+ titleViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }?.restoreSpannableFromMap()
585
+ bodyViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }?.restoreSpannableFromMap()
586
+ callToActionViewId?.let { adViewContainer.findViewById<ReactTextView>(it) }
587
+ ?.restoreSpannableFromMap()
588
+ }
589
+
341
590
  private fun ReactTextView.saveSpannableToMap() {
342
591
  val text: CharSequence? = getText()
343
592
 
@@ -0,0 +1,123 @@
1
+ package com.daro.reactnative.view
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+ import android.view.ViewGroup
6
+ import android.view.ViewTreeObserver
7
+ import android.widget.FrameLayout
8
+
9
+ class DaroMediaView(context: Context) : FrameLayout(context) {
10
+
11
+ private var relayoutPending = false
12
+ private var scanZeroCount = 0
13
+ private var scanViewCount = 0
14
+ private var lastZeroSignature = 0L
15
+ private var zeroSizeRelayoutAttempts = 0
16
+
17
+ private companion object {
18
+ private const val MAX_ZERO_SIZE_RELAYOUT_ATTEMPTS = 3
19
+ }
20
+
21
+ private val relayoutRunnable = Runnable {
22
+ relayoutPending = false
23
+ relayoutChildren()
24
+ }
25
+
26
+ private val preDrawListener = ViewTreeObserver.OnPreDrawListener {
27
+ if (width > 0 && height > 0 && childCount > 0) {
28
+ scanZeroCount = 0
29
+ scanViewCount = 0
30
+ scanSubtree(this)
31
+
32
+ if (scanZeroCount == 0) {
33
+ lastZeroSignature = 0L
34
+ zeroSizeRelayoutAttempts = 0
35
+ } else {
36
+ val signature = (scanZeroCount.toLong() shl 32) or scanViewCount.toLong()
37
+ if (signature != lastZeroSignature) {
38
+ lastZeroSignature = signature
39
+ zeroSizeRelayoutAttempts = 0
40
+ }
41
+
42
+ if (zeroSizeRelayoutAttempts < MAX_ZERO_SIZE_RELAYOUT_ATTEMPTS) {
43
+ zeroSizeRelayoutAttempts++
44
+ relayoutChildren()
45
+ }
46
+ }
47
+ }
48
+ true
49
+ }
50
+
51
+ override fun requestLayout() {
52
+ super.requestLayout()
53
+ scheduleRelayout()
54
+ }
55
+
56
+ override fun onViewAdded(child: View?) {
57
+ super.onViewAdded(child)
58
+ relayoutChildren()
59
+ scheduleRelayout()
60
+ }
61
+
62
+ override fun onAttachedToWindow() {
63
+ super.onAttachedToWindow()
64
+ relayoutChildren()
65
+ relayoutPending = false
66
+ removeCallbacks(relayoutRunnable)
67
+ scheduleRelayout()
68
+ viewTreeObserver.addOnPreDrawListener(preDrawListener)
69
+ }
70
+
71
+ override fun onDetachedFromWindow() {
72
+ viewTreeObserver.removeOnPreDrawListener(preDrawListener)
73
+ removeCallbacks(relayoutRunnable)
74
+ relayoutPending = false
75
+ super.onDetachedFromWindow()
76
+ }
77
+
78
+ private fun scheduleRelayout() {
79
+ lastZeroSignature = 0L
80
+ zeroSizeRelayoutAttempts = 0
81
+
82
+ if (relayoutPending) return
83
+
84
+ relayoutPending = true
85
+ post(relayoutRunnable)
86
+ }
87
+
88
+ private fun relayoutChildren() {
89
+ val width = width
90
+ val height = height
91
+
92
+ if (width <= 0 || height <= 0) return
93
+
94
+ for (i in 0 until childCount) {
95
+ val child = getChildAt(i)
96
+ if (child.visibility == GONE) continue
97
+
98
+ forceLayoutRecursive(child)
99
+ child.measure(
100
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
101
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
102
+ )
103
+ child.layout(0, 0, width, height)
104
+ }
105
+ }
106
+
107
+ private fun forceLayoutRecursive(view: View) {
108
+ view.forceLayout()
109
+ if (view is ViewGroup) {
110
+ for (i in 0 until view.childCount) forceLayoutRecursive(view.getChildAt(i))
111
+ }
112
+ }
113
+
114
+ private fun scanSubtree(parent: ViewGroup) {
115
+ for (i in 0 until parent.childCount) {
116
+ val child = parent.getChildAt(i)
117
+ scanViewCount++
118
+ if (child.visibility != View.VISIBLE) continue
119
+ if (child.width <= 0 || child.height <= 0) scanZeroCount++
120
+ if (child is ViewGroup) scanSubtree(child)
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,6 @@
1
+ package com.daro.reactnative
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+
5
+ abstract class DaroMModuleSpec(reactContext: ReactApplicationContext) :
6
+ NativeDaroModuleSpec(reactContext)