react-native-morph-card 0.1.3 → 0.1.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.
@@ -228,7 +228,8 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
228
228
  * Walk the view tree and hide any screen container that isn't already known.
229
229
  * This catches modal screens added to separate ScreenStacks (e.g. transparentModal).
230
230
  */
231
- private fun hideNewScreenContainers(root: ViewGroup, knownScreens: Set<View>) {
231
+ private fun hideNewScreenContainers(root: ViewGroup, knownScreens: Set<View>): Int {
232
+ var count = 0
232
233
  fun walk(group: ViewGroup) {
233
234
  val name = group.javaClass.name
234
235
  if (name.contains("ScreenStack") || name.contains("ScreenContainer")) {
@@ -236,6 +237,7 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
236
237
  val child = group.getChildAt(i)
237
238
  if (!knownScreens.contains(child) && child.visibility == View.VISIBLE) {
238
239
  child.visibility = View.INVISIBLE
240
+ count++
239
241
  Log.d(TAG, "preDraw: hid new screen ${child.javaClass.simpleName} in ${group.javaClass.simpleName}")
240
242
  }
241
243
  }
@@ -246,6 +248,7 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
246
248
  }
247
249
  }
248
250
  walk(root)
251
+ return count
249
252
  }
250
253
 
251
254
  /**
@@ -353,8 +356,16 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
353
356
  val knownScreens = collectExistingScreens(decorView)
354
357
  Log.d(TAG, "prepareExpand: tracking ${knownScreens.size} existing screens")
355
358
  val pdListener = ViewTreeObserver.OnPreDrawListener {
356
- hideNewScreenContainers(decorView, knownScreens)
357
- true
359
+ val hidCount = hideNewScreenContainers(decorView, knownScreens)
360
+ if (hidCount > 0) {
361
+ // Cancel this draw frame — the new screen was visible and we just hid it.
362
+ // Returning false prevents this frame from rendering, so the screen
363
+ // is never shown. The next frame will re-check and proceed.
364
+ Log.d(TAG, "preDraw: cancelled draw frame (hid $hidCount screens)")
365
+ false
366
+ } else {
367
+ true
368
+ }
358
369
  }
359
370
  decorView.viewTreeObserver.addOnPreDrawListener(pdListener)
360
371
  preDrawListener = pdListener
@@ -362,16 +373,41 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
362
373
  // Capture snapshot
363
374
  val cardImage = captureSnapshot()
364
375
 
365
- // Create a full-screen overlay container that blocks the modal target screen
366
- // from being visible. We use a transparent overlay the source screen remains
367
- // visible underneath. The overlay just ensures our card wrapper is above everything.
376
+ // Create a full-screen overlay that blocks the modal target screen from
377
+ // being visible. We use PixelCopy to capture the current screen with
378
+ // hardware rendering preserved (clipToOutline, borderRadius, etc.).
368
379
  val fullScreenOverlay = FrameLayout(context)
369
- fullScreenOverlay.layoutParams = FrameLayout.LayoutParams(
370
- FrameLayout.LayoutParams.MATCH_PARENT,
371
- FrameLayout.LayoutParams.MATCH_PARENT
372
- )
373
- // Intercept all touches while overlay is up
380
+ fullScreenOverlay.layoutParams = FrameLayout.LayoutParams(decorView.width, decorView.height)
374
381
  fullScreenOverlay.isClickable = true
382
+ // Ensure the overlay renders above any views with elevation (e.g. ScreenStack children)
383
+ fullScreenOverlay.translationZ = 1000f
384
+
385
+ // PixelCopy captures from the surface (hardware-rendered, preserves outlines).
386
+ // We use a background HandlerThread for the callback to avoid deadlocking main.
387
+ val window = (context as? android.app.Activity)?.window
388
+ if (window != null) {
389
+ val blockerBitmap = Bitmap.createBitmap(decorView.width, decorView.height, Bitmap.Config.ARGB_8888)
390
+ val copyThread = android.os.HandlerThread("PixelCopyThread")
391
+ copyThread.start()
392
+ val copyHandler = Handler(copyThread.looper)
393
+ val latch = java.util.concurrent.CountDownLatch(1)
394
+ android.view.PixelCopy.request(window, blockerBitmap, { result ->
395
+ Log.d(TAG, "prepareExpand: PixelCopy result=$result (0=SUCCESS)")
396
+ latch.countDown()
397
+ }, copyHandler)
398
+ // Wait for the copy (typically <5ms)
399
+ try { latch.await(100, java.util.concurrent.TimeUnit.MILLISECONDS) } catch (_: Exception) {}
400
+ copyThread.quitSafely()
401
+
402
+ val blockerImg = ImageView(context)
403
+ blockerImg.setImageBitmap(blockerBitmap)
404
+ blockerImg.scaleType = ImageView.ScaleType.FIT_XY
405
+ blockerImg.layoutParams = FrameLayout.LayoutParams(
406
+ FrameLayout.LayoutParams.MATCH_PARENT,
407
+ FrameLayout.LayoutParams.MATCH_PARENT
408
+ )
409
+ fullScreenOverlay.addView(blockerImg)
410
+ }
375
411
 
376
412
  // Create card overlay at source position (on top of screen capture)
377
413
  val bgColor = cardBgColor
@@ -526,11 +562,17 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
526
562
  }
527
563
 
528
564
  // Crossfade: at 15% of animation, make target screen VISIBLE with alpha=0
529
- // then fade alpha to 1 over 50% of duration
565
+ // then fade alpha to 1 over 50% of duration. Also remove the blocker image.
530
566
  val targetScreen = targetScreenContainerRef?.get()
531
- val sourceScreen = sourceScreenContainerRef?.get()
532
- if (targetScreen != null && targetScreen !== sourceScreen) {
567
+ val sourceScreen2 = sourceScreenContainerRef?.get()
568
+ // Find the blocker image (first child of the full-screen overlay, before the card wrapper)
569
+ val blockerView = if (wrapper.childCount > 1) wrapper.getChildAt(0) else null
570
+ if (targetScreen != null && targetScreen !== sourceScreen2) {
533
571
  mainHandler.postDelayed({
572
+ // Remove blocker — source screen is visible underneath
573
+ if (blockerView != null && blockerView.tag != "morphCardWrapper") {
574
+ (blockerView.parent as? ViewGroup)?.removeView(blockerView)
575
+ }
534
576
  // Switch from INVISIBLE to VISIBLE but with alpha=0
535
577
  targetScreen.alpha = 0f
536
578
  targetScreen.visibility = View.VISIBLE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-morph-card",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Native card-to-modal morph transition for React Native. iOS App Store-style expand animation.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",