react-native-screens 4.10.0-beta.1 → 4.10.0-beta.3

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 (30) hide show
  1. package/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +3 -8
  2. package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +111 -15
  3. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +11 -2
  4. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +174 -199
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +31 -285
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +89 -54
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigShadowNode.kt +3 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +1 -1
  10. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +257 -3
  11. package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +7 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/utils/FragmentTransactionKt.kt +103 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/utils/InsetsKt.kt +31 -0
  14. package/android/src/main/java/com/swmansion/rnscreens/utils/PaddingBundle.kt +1 -0
  15. package/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +14 -5
  16. package/ios/RNSFullWindowOverlay.mm +6 -6
  17. package/lib/commonjs/components/Screen.js +5 -3
  18. package/lib/commonjs/components/Screen.js.map +1 -1
  19. package/lib/commonjs/native-stack/views/NativeStackView.js +3 -5
  20. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  21. package/lib/module/components/Screen.js +5 -3
  22. package/lib/module/components/Screen.js.map +1 -1
  23. package/lib/module/native-stack/views/NativeStackView.js +3 -5
  24. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  25. package/lib/typescript/components/Screen.d.ts.map +1 -1
  26. package/lib/typescript/native-stack/views/NativeStackView.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/Screen.tsx +7 -5
  29. package/src/native-stack/views/NativeStackView.tsx +11 -12
  30. package/android/src/main/java/com/swmansion/rnscreens/NativeDismissalObserver.kt +0 -12
@@ -9,8 +9,75 @@ import com.facebook.react.uimanager.UIManagerHelper
9
9
  import com.swmansion.rnscreens.Screen.StackAnimation
10
10
  import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
11
11
  import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
12
+ import com.swmansion.rnscreens.utils.setTweenAnimations
12
13
  import java.util.Collections
13
14
  import kotlin.collections.ArrayList
15
+ import kotlin.math.max
16
+
17
+ internal interface ChildDrawingOrderStrategy {
18
+ /**
19
+ * Mutates the list of draw operations **in-place**.
20
+ */
21
+ fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>)
22
+
23
+ /**
24
+ * Enables the given strategy. When enabled - the strategy **might** mutate the operations
25
+ * list passed to `apply` method.
26
+ */
27
+ fun enable()
28
+
29
+ /**
30
+ * Disables the given strategy - even when `apply` is called it **must not** produce
31
+ * any side effect (it must not manipulate the drawing operations list passed to `apply` method).
32
+ */
33
+ fun disable()
34
+
35
+ fun isEnabled(): Boolean
36
+ }
37
+
38
+ internal abstract class ChildDrawingOrderStrategyBase(
39
+ var enabled: Boolean = false,
40
+ ) : ChildDrawingOrderStrategy {
41
+ override fun enable() {
42
+ enabled = true
43
+ }
44
+
45
+ override fun disable() {
46
+ enabled = false
47
+ }
48
+
49
+ override fun isEnabled() = enabled
50
+ }
51
+
52
+ internal class SwapLastTwo : ChildDrawingOrderStrategyBase() {
53
+ override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
54
+ if (!isEnabled()) {
55
+ return
56
+ }
57
+ if (drawingOperations.size >= 2) {
58
+ Collections.swap(drawingOperations, drawingOperations.lastIndex, drawingOperations.lastIndex - 1)
59
+ }
60
+ }
61
+ }
62
+
63
+ internal class ReverseOrderInRange(
64
+ val range: IntRange,
65
+ ) : ChildDrawingOrderStrategyBase() {
66
+ override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
67
+ if (!isEnabled()) {
68
+ return
69
+ }
70
+
71
+ var startRange = range.start
72
+ var endRange = range.endInclusive
73
+
74
+ while (startRange < endRange) {
75
+ Collections.swap(drawingOperations, startRange, endRange)
76
+ startRange += 1
77
+ endRange -= 1
78
+ }
79
+ }
80
+ }
14
81
 
15
82
  class ScreenStack(
16
83
  context: Context?,
@@ -21,9 +88,10 @@ class ScreenStack(
21
88
  private var drawingOps: MutableList<DrawingOp> = ArrayList()
22
89
  private var topScreenWrapper: ScreenStackFragmentWrapper? = null
23
90
  private var removalTransitionStarted = false
24
- private var isDetachingCurrentScreen = false
25
- private var reverseLastTwoChildren = false
26
91
  private var previousChildrenCount = 0
92
+
93
+ private var childDrawingOrderStrategy: ChildDrawingOrderStrategy? = null
94
+
27
95
  var goingForward = false
28
96
 
29
97
  /**
@@ -55,11 +123,13 @@ class ScreenStack(
55
123
 
56
124
  override fun startViewTransition(view: View) {
57
125
  super.startViewTransition(view)
126
+ childDrawingOrderStrategy?.enable()
58
127
  removalTransitionStarted = true
59
128
  }
60
129
 
61
130
  override fun endViewTransition(view: View) {
62
131
  super.endViewTransition(view)
132
+ childDrawingOrderStrategy?.disable()
63
133
  if (removalTransitionStarted) {
64
134
  removalTransitionStarted = false
65
135
  dispatchOnFinishTransitioning()
@@ -97,208 +167,131 @@ class ScreenStack(
97
167
  // when all screens are dismissed and no screen is to be displayed on top. We need to gracefully
98
168
  // handle the case of newTop being NULL, which happens in several places below
99
169
  var newTop: ScreenFragmentWrapper? = null // newTop is nullable, see the above comment ^
100
- var visibleBottom: ScreenFragmentWrapper? =
101
- null // this is only set if newTop has one of transparent presentation modes
102
- isDetachingCurrentScreen = false // we reset it so the previous value is not used by mistake
103
-
104
- for (i in screenWrappers.indices.reversed()) {
105
- val screenWrapper = getScreenFragmentWrapperAt(i)
106
- if (!dismissedWrappers.contains(screenWrapper) && screenWrapper.screen.activityState !== Screen.ActivityState.INACTIVE) {
107
- if (newTop == null) {
108
- newTop = screenWrapper
109
- } else {
110
- visibleBottom = screenWrapper
111
- }
112
- if (!screenWrapper.screen.isTransparent()) {
113
- break
114
- }
115
- }
170
+
171
+ // this is only set if newTop has one of transparent presentation modes
172
+ var visibleBottom: ScreenFragmentWrapper? = null
173
+
174
+ // reset, to not use previously set strategy by mistake
175
+ childDrawingOrderStrategy = null
176
+
177
+ // Determine new first & last visible screens.
178
+ // Scope function to limit the scope of locals.
179
+ run {
180
+ val notDismissedWrappers =
181
+ screenWrappers
182
+ .asReversed()
183
+ .asSequence()
184
+ .filter { !dismissedWrappers.contains(it) && it.screen.activityState !== Screen.ActivityState.INACTIVE }
185
+
186
+ newTop = notDismissedWrappers.firstOrNull()
187
+ visibleBottom =
188
+ notDismissedWrappers
189
+ .dropWhile { it.screen.isTransparent() }
190
+ .firstOrNull()
191
+ ?.takeUnless { it === newTop }
116
192
  }
117
193
 
118
194
  var shouldUseOpenAnimation = true
119
195
  var stackAnimation: StackAnimation? = null
120
- if (!stack.contains(newTop)) {
196
+
197
+ val newTopAlreadyInStack = stack.contains(newTop)
198
+ val topScreenWillChange = newTop !== topScreenWrapper
199
+
200
+ if (newTop != null && !newTopAlreadyInStack) {
121
201
  // if new top screen wasn't on stack we do "open animation" so long it is not the very first
122
202
  // screen on stack
123
- if (topScreenWrapper != null && newTop != null) {
203
+ if (topScreenWrapper != null) {
124
204
  // there was some other screen attached before
125
205
  // if the previous top screen does not exist anymore and the new top was not on the stack
126
206
  // before, probably replace or reset was called, so we play the "close animation".
127
207
  // Otherwise it's open animation
128
- val containsTopScreen = topScreenWrapper?.let { screenWrappers.contains(it) } == true
208
+ val previousTopScreenRemainsInStack = topScreenWrapper?.let { screenWrappers.contains(it) } == true
129
209
  val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH
130
- shouldUseOpenAnimation = containsTopScreen || isPushReplace
210
+ shouldUseOpenAnimation = previousTopScreenRemainsInStack || isPushReplace
131
211
  // if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one
132
212
  stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else topScreenWrapper?.screen?.stackAnimation
133
- } else if (topScreenWrapper == null && newTop != null) {
213
+ } else {
134
214
  // mTopScreen was not present before so newTop is the first screen added to a stack
135
215
  // and we don't want the animation when it is entering
136
216
  stackAnimation = StackAnimation.NONE
137
217
  goingForward = true
138
218
  }
139
- } else if (topScreenWrapper != null && topScreenWrapper != newTop) {
219
+ } else if (newTop != null && topScreenWrapper != null && topScreenWillChange) {
140
220
  // otherwise if we are performing top screen change we do "close animation"
141
221
  shouldUseOpenAnimation = false
142
222
  stackAnimation = topScreenWrapper?.screen?.stackAnimation
143
223
  }
144
224
 
145
- createTransaction().let {
146
- // animation logic start
147
- if (stackAnimation != null) {
148
- if (shouldUseOpenAnimation) {
149
- when (stackAnimation) {
150
- StackAnimation.DEFAULT ->
151
- it.setCustomAnimations(
152
- R.anim.rns_default_enter_in,
153
- R.anim.rns_default_enter_out,
154
- )
155
-
156
- StackAnimation.NONE ->
157
- it.setCustomAnimations(
158
- R.anim.rns_no_animation_20,
159
- R.anim.rns_no_animation_20,
160
- )
161
-
162
- StackAnimation.FADE ->
163
- it.setCustomAnimations(
164
- R.anim.rns_fade_in,
165
- R.anim.rns_fade_out,
166
- )
167
-
168
- StackAnimation.SLIDE_FROM_RIGHT ->
169
- it.setCustomAnimations(
170
- R.anim.rns_slide_in_from_right,
171
- R.anim.rns_slide_out_to_left,
172
- )
173
- StackAnimation.SLIDE_FROM_LEFT ->
174
- it.setCustomAnimations(
175
- R.anim.rns_slide_in_from_left,
176
- R.anim.rns_slide_out_to_right,
177
- )
178
- StackAnimation.SLIDE_FROM_BOTTOM ->
179
- it.setCustomAnimations(
180
- R.anim.rns_slide_in_from_bottom,
181
- R.anim.rns_no_animation_medium,
182
- )
183
- StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350)
184
- StackAnimation.IOS_FROM_RIGHT ->
185
- it.setCustomAnimations(
186
- R.anim.rns_ios_from_right_foreground_open,
187
- R.anim.rns_ios_from_right_background_open,
188
- )
189
- StackAnimation.IOS_FROM_LEFT ->
190
- it.setCustomAnimations(
191
- R.anim.rns_ios_from_left_foreground_open,
192
- R.anim.rns_ios_from_left_background_open,
193
- )
194
- }
195
- } else {
196
- when (stackAnimation) {
197
- StackAnimation.DEFAULT ->
198
- it.setCustomAnimations(
199
- R.anim.rns_default_exit_in,
200
- R.anim.rns_default_exit_out,
201
- )
202
-
203
- StackAnimation.NONE ->
204
- it.setCustomAnimations(
205
- R.anim.rns_no_animation_20,
206
- R.anim.rns_no_animation_20,
207
- )
208
-
209
- StackAnimation.FADE ->
210
- it.setCustomAnimations(
211
- R.anim.rns_fade_in,
212
- R.anim.rns_fade_out,
213
- )
214
-
215
- StackAnimation.SLIDE_FROM_RIGHT ->
216
- it.setCustomAnimations(
217
- R.anim.rns_slide_in_from_left,
218
- R.anim.rns_slide_out_to_right,
219
- )
220
- StackAnimation.SLIDE_FROM_LEFT ->
221
- it.setCustomAnimations(
222
- R.anim.rns_slide_in_from_right,
223
- R.anim.rns_slide_out_to_left,
224
- )
225
- StackAnimation.SLIDE_FROM_BOTTOM ->
226
- it.setCustomAnimations(
227
- R.anim.rns_no_animation_medium,
228
- R.anim.rns_slide_out_to_bottom,
229
- )
230
- StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom)
231
- StackAnimation.IOS_FROM_RIGHT ->
232
- it.setCustomAnimations(
233
- R.anim.rns_ios_from_right_background_close,
234
- R.anim.rns_ios_from_right_foreground_close,
235
- )
236
- StackAnimation.IOS_FROM_LEFT ->
237
- it.setCustomAnimations(
238
- R.anim.rns_ios_from_left_background_close,
239
- R.anim.rns_ios_from_left_foreground_close,
240
- )
241
- }
242
- }
243
- }
244
- // animation logic end
245
- goingForward = shouldUseOpenAnimation
246
-
247
- if (shouldUseOpenAnimation &&
248
- newTop != null &&
249
- needsDrawReordering(newTop, stackAnimation) &&
250
- visibleBottom == null
251
- ) {
252
- // When using an open animation in which two screens overlap (eg. fade_from_bottom or
253
- // slide_from_bottom), we want to draw the previous screen under the new one,
254
- // which is apparently not the default option. Android always draws the disappearing view
255
- // on top of the appearing one. We then reverse the order of the views so the new screen
256
- // appears on top of the previous one. You can read more about in the comment
257
- // for the code we use to change that behavior:
258
- // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18
259
- // Note: This should not be set in case there is only a single screen in stack or animation `none` is used. Atm needsDrawReordering implementation guards that assuming that first screen on stack uses `NONE` animation.
260
- isDetachingCurrentScreen = true
225
+ goingForward = shouldUseOpenAnimation
226
+
227
+ if (shouldUseOpenAnimation &&
228
+ newTop != null &&
229
+ needsDrawReordering(newTop, stackAnimation) &&
230
+ visibleBottom == null
231
+ ) {
232
+ // When using an open animation in which two screens overlap (eg. fade_from_bottom or
233
+ // slide_from_bottom), we want to draw the previous screen under the new one,
234
+ // which is apparently not the default option. Android always draws the disappearing view
235
+ // on top of the appearing one. We then reverse the order of the views so the new screen
236
+ // appears on top of the previous one. You can read more about in the comment
237
+ // for the code we use to change that behavior:
238
+ // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18
239
+ // Note: This should not be set in case there is only a single screen in stack or animation `none` is used.
240
+ // Atm needsDrawReordering implementation guards that assuming that first screen on stack uses `NONE` animation.
241
+ childDrawingOrderStrategy = SwapLastTwo()
242
+ } else if (newTop != null &&
243
+ newTopAlreadyInStack &&
244
+ topScreenWrapper?.screen?.isTransparent() == true &&
245
+ newTop.screen.isTransparent() == false
246
+ ) {
247
+ // In case where we dismiss multiple transparent views we want to ensure
248
+ // that they are drawn in correct order - Android swaps them by default,
249
+ // so we need to swap the swap to unswap :D
250
+ val dismissedTransparentScreenApproxCount =
251
+ stack
252
+ .asReversed()
253
+ .asSequence()
254
+ .takeWhile {
255
+ it !== newTop &&
256
+ it.screen.isTransparent()
257
+ }.count()
258
+ if (dismissedTransparentScreenApproxCount > 1) {
259
+ childDrawingOrderStrategy =
260
+ ReverseOrderInRange(max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1, 0)..stack.lastIndex)
261
261
  }
262
+ }
262
263
 
263
- // remove all screens previously on stack
264
- for (fragmentWrapper in stack) {
265
- if (!screenWrappers.contains(fragmentWrapper) || dismissedWrappers.contains(fragmentWrapper)) {
266
- it.remove(fragmentWrapper.fragment)
267
- }
268
- }
269
- for (fragmentWrapper in screenWrappers) {
270
- // Stop detaching screens when reaching visible bottom. All screens above bottom should be
271
- // visible.
272
- if (fragmentWrapper === visibleBottom) {
273
- break
274
- }
275
- // detach all screens that should not be visible
276
- if ((fragmentWrapper !== newTop && !dismissedWrappers.contains(fragmentWrapper)) ||
277
- fragmentWrapper.screen.activityState === Screen.ActivityState.INACTIVE
278
- ) {
279
- it.remove(fragmentWrapper.fragment)
280
- }
264
+ createTransaction().let { transaction ->
265
+ if (stackAnimation != null) {
266
+ transaction.setTweenAnimations(stackAnimation, shouldUseOpenAnimation)
281
267
  }
282
268
 
269
+ // Remove all screens that are currently on stack, but should be dismissed, because they're
270
+ // no longer rendered or were dismissed natively.
271
+ stack
272
+ .asSequence()
273
+ .filter { wrapper -> !screenWrappers.contains(wrapper) || dismissedWrappers.contains(wrapper) }
274
+ .forEach { wrapper -> transaction.remove(wrapper.fragment) }
275
+
276
+ // Remove all screens underneath visibleBottom && these marked for preload, but keep newTop.
277
+ screenWrappers
278
+ .asSequence()
279
+ .takeWhile { it !== visibleBottom }
280
+ .filter { (it !== newTop && !dismissedWrappers.contains(it)) || it.screen.activityState === Screen.ActivityState.INACTIVE }
281
+ .forEach { wrapper -> transaction.remove(wrapper.fragment) }
282
+
283
283
  // attach screens that just became visible
284
284
  if (visibleBottom != null && !visibleBottom.fragment.isAdded) {
285
285
  val top = newTop
286
- var beneathVisibleBottom = true
287
- for (fragmentWrapper in screenWrappers) {
288
- // ignore all screens beneath the visible bottom
289
- if (beneathVisibleBottom) {
290
- beneathVisibleBottom =
291
- if (fragmentWrapper === visibleBottom) {
292
- false
293
- } else {
294
- continue
295
- }
296
- }
297
- // when first visible screen found, make all screens after that visible
298
- it.add(id, fragmentWrapper.fragment).runOnCommit {
299
- top?.screen?.bringToFront()
286
+ screenWrappers
287
+ .asSequence()
288
+ .dropWhile { it !== visibleBottom } // ignore all screens beneath the visible bottom
289
+ .forEach { wrapper ->
290
+ // TODO: It should be enough to dispatch this on commit action once.
291
+ transaction.add(id, wrapper.fragment).runOnCommit {
292
+ top?.screen?.bringToFront()
293
+ }
300
294
  }
301
- }
302
295
  } else if (newTop != null && !newTop.fragment.isAdded) {
303
296
  if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && newTop.screen.isSheetFitToContents()) {
304
297
  // On old architecture the content wrapper might not have received its frame yet,
@@ -306,15 +299,14 @@ class ScreenStack(
306
299
  // we delay the transition and trigger it after views receive the layout.
307
300
  newTop.fragment.postponeEnterTransition()
308
301
  }
309
- it.add(id, newTop.fragment)
302
+ transaction.add(id, newTop.fragment)
310
303
  }
311
304
  topScreenWrapper = newTop as? ScreenStackFragmentWrapper
312
305
  stack.clear()
313
- stack.addAll(screenWrappers.map { it as ScreenStackFragmentWrapper })
306
+ stack.addAll(screenWrappers.asSequence().map { it as ScreenStackFragmentWrapper })
314
307
 
315
308
  turnOffA11yUnderTransparentScreen(visibleBottom)
316
-
317
- it.commitNowAllowingStateLoss()
309
+ transaction.commitNowAllowingStateLoss()
318
310
  }
319
311
  }
320
312
 
@@ -346,23 +338,6 @@ class ScreenStack(
346
338
  stack.forEach { it.onContainerUpdate() }
347
339
  }
348
340
 
349
- // below methods are taken from
350
- // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L43
351
- // and are used to swap the order of drawing views when navigating forward with the transitions
352
- // that are making transitioning fragments appear one on another. See more info in the comment to
353
- // the linked class.
354
- override fun removeView(view: View) {
355
- // we set this property to reverse the order of drawing views
356
- // when we want to push new fragment on top of the previous one and their animations collide.
357
- // More information in:
358
- // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L17
359
- if (isDetachingCurrentScreen) {
360
- isDetachingCurrentScreen = false
361
- reverseLastTwoChildren = true
362
- }
363
- super.removeView(view)
364
- }
365
-
366
341
  private fun drawAndRelease() {
367
342
  // We make a copy of the drawingOps and use it to dispatch draws in order to be sure
368
343
  // that we do not modify the original list. There are cases when `op.draw` can call
@@ -381,12 +356,12 @@ class ScreenStack(
381
356
 
382
357
  // check the view removal is completed (by comparing the previous children count)
383
358
  if (drawingOps.size < previousChildrenCount) {
384
- reverseLastTwoChildren = false
359
+ childDrawingOrderStrategy = null
385
360
  }
386
361
  previousChildrenCount = drawingOps.size
387
- if (reverseLastTwoChildren && drawingOps.size >= 2) {
388
- Collections.swap(drawingOps, drawingOps.size - 1, drawingOps.size - 2)
389
- }
362
+
363
+ childDrawingOrderStrategy?.apply(drawingOps)
364
+
390
365
  drawAndRelease()
391
366
  }
392
367
 
@@ -415,7 +390,7 @@ class ScreenStack(
415
390
  // See: https://developer.android.com/about/versions/15/behavior-changes-15?hl=en#openjdk-api-changes
416
391
  private fun obtainDrawingOp(): DrawingOp = if (drawingOpPool.isEmpty()) DrawingOp() else drawingOpPool.removeAt(drawingOpPool.lastIndex)
417
392
 
418
- private inner class DrawingOp {
393
+ internal inner class DrawingOp {
419
394
  var canvas: Canvas? = null
420
395
  var child: View? = null
421
396
  var drawingTime: Long = 0