react-native-morph-card 0.1.2 → 0.1.4

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.
@@ -362,7 +362,46 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
362
362
  // Capture snapshot
363
363
  val cardImage = captureSnapshot()
364
364
 
365
- // Create overlay at source position
365
+ // Create a full-screen overlay that blocks the modal target screen from
366
+ // being visible. We use PixelCopy to capture the current screen with
367
+ // hardware rendering preserved (clipToOutline, borderRadius, etc.).
368
+ val fullScreenOverlay = FrameLayout(context)
369
+ fullScreenOverlay.layoutParams = FrameLayout.LayoutParams(
370
+ FrameLayout.LayoutParams.MATCH_PARENT,
371
+ FrameLayout.LayoutParams.MATCH_PARENT
372
+ )
373
+ fullScreenOverlay.isClickable = true
374
+
375
+ // PixelCopy captures from the surface (hardware-rendered, preserves outlines).
376
+ // We use a background HandlerThread for the callback to avoid deadlocking main.
377
+ val window = (context as? android.app.Activity)?.window
378
+ if (window != null) {
379
+ val blockerBitmap = Bitmap.createBitmap(decorView.width, decorView.height, Bitmap.Config.ARGB_8888)
380
+ val copyThread = android.os.HandlerThread("PixelCopyThread")
381
+ copyThread.start()
382
+ val copyHandler = Handler(copyThread.looper)
383
+ val latch = java.util.concurrent.CountDownLatch(1)
384
+ android.view.PixelCopy.request(window, blockerBitmap, { result ->
385
+ if (result != android.view.PixelCopy.SUCCESS) {
386
+ Log.d(TAG, "prepareExpand: PixelCopy failed with result=$result")
387
+ }
388
+ latch.countDown()
389
+ }, copyHandler)
390
+ // Wait for the copy (typically <5ms)
391
+ try { latch.await(100, java.util.concurrent.TimeUnit.MILLISECONDS) } catch (_: Exception) {}
392
+ copyThread.quitSafely()
393
+
394
+ val blockerImg = ImageView(context)
395
+ blockerImg.setImageBitmap(blockerBitmap)
396
+ blockerImg.scaleType = ImageView.ScaleType.FIT_XY
397
+ blockerImg.layoutParams = FrameLayout.LayoutParams(
398
+ FrameLayout.LayoutParams.MATCH_PARENT,
399
+ FrameLayout.LayoutParams.MATCH_PARENT
400
+ )
401
+ fullScreenOverlay.addView(blockerImg)
402
+ }
403
+
404
+ // Create card overlay at source position (on top of screen capture)
366
405
  val bgColor = cardBgColor
367
406
  val wrapper = FrameLayout(context)
368
407
  wrapper.layoutParams = FrameLayout.LayoutParams(cardWidth.toInt(), cardHeight.toInt())
@@ -380,9 +419,11 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
380
419
  content.scaleType = ImageView.ScaleType.FIT_XY
381
420
  content.layoutParams = FrameLayout.LayoutParams(cardWidth.toInt(), cardHeight.toInt())
382
421
  wrapper.addView(content)
422
+ wrapper.tag = "morphCardWrapper"
423
+ fullScreenOverlay.addView(wrapper)
383
424
 
384
- decorView.addView(wrapper)
385
- overlayContainer = wrapper
425
+ decorView.addView(fullScreenOverlay)
426
+ overlayContainer = fullScreenOverlay
386
427
 
387
428
  // Hide source card — overlay covers it
388
429
  alpha = 0f
@@ -468,7 +509,9 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
468
509
  Log.d(TAG, "animateExpand: pendingTarget w=${pendingTargetWidth} h=${pendingTargetHeight} br=${pendingTargetBorderRadius}")
469
510
 
470
511
  val dur = duration.toLong()
471
- val content = if (wrapper.childCount > 0) wrapper.getChildAt(0) else null
512
+ // Find the card wrapper inside the full-screen overlay
513
+ val cardWrapper = wrapper.findViewWithTag<FrameLayout>("morphCardWrapper") ?: wrapper
514
+ val content = if (cardWrapper.childCount > 0) cardWrapper.getChildAt(0) else null
472
515
 
473
516
  // Compute content offset for wrapper mode
474
517
  val contentCx = if (hasWrapper && pendingContentCentered) (targetWidthPx - cardWidth) / 2f else 0f
@@ -486,13 +529,14 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
486
529
 
487
530
  animator.addUpdateListener { anim ->
488
531
  val t = anim.animatedValue as Float
489
- wrapper.x = lerp(cardLeft, targetLeft, t)
490
- wrapper.y = lerp(cardTop, targetTop, t)
491
- val lp = wrapper.layoutParams
532
+
533
+ cardWrapper.x = lerp(cardLeft, targetLeft, t)
534
+ cardWrapper.y = lerp(cardTop, targetTop, t)
535
+ val lp = cardWrapper.layoutParams
492
536
  lp.width = lerp(cardWidth, targetWidthPx, t).toInt()
493
537
  lp.height = lerp(cardHeight, targetHeightPx, t).toInt()
494
- wrapper.layoutParams = lp
495
- setRoundedCorners(wrapper, lerp(cardCornerRadiusPx, targetCornerRadiusPx, t))
538
+ cardWrapper.layoutParams = lp
539
+ setRoundedCorners(cardWrapper, lerp(cardCornerRadiusPx, targetCornerRadiusPx, t))
496
540
 
497
541
  if (content != null) {
498
542
  if (hasWrapper) {
@@ -510,11 +554,17 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
510
554
  }
511
555
 
512
556
  // Crossfade: at 15% of animation, make target screen VISIBLE with alpha=0
513
- // then fade alpha to 1 over 50% of duration
557
+ // then fade alpha to 1 over 50% of duration. Also remove the blocker image.
514
558
  val targetScreen = targetScreenContainerRef?.get()
515
- val sourceScreen = sourceScreenContainerRef?.get()
516
- if (targetScreen != null && targetScreen !== sourceScreen) {
559
+ val sourceScreen2 = sourceScreenContainerRef?.get()
560
+ // Find the blocker image (first child of the full-screen overlay, before the card wrapper)
561
+ val blockerView = if (wrapper.childCount > 1) wrapper.getChildAt(0) else null
562
+ if (targetScreen != null && targetScreen !== sourceScreen2) {
517
563
  mainHandler.postDelayed({
564
+ // Remove blocker — source screen is visible underneath
565
+ if (blockerView != null && blockerView.tag != "morphCardWrapper") {
566
+ (blockerView.parent as? ViewGroup)?.removeView(blockerView)
567
+ }
518
568
  // Switch from INVISIBLE to VISIBLE but with alpha=0
519
569
  targetScreen.alpha = 0f
520
570
  targetScreen.visibility = View.VISIBLE
@@ -569,8 +619,9 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
569
619
  return
570
620
  }
571
621
 
572
- // Get the bitmap from the overlay
573
- val origImg = if (overlay.childCount > 0) overlay.getChildAt(0) as? ImageView else null
622
+ // Get the bitmap from the card wrapper inside the overlay
623
+ val cardWrap = overlay.findViewWithTag<FrameLayout>("morphCardWrapper") ?: overlay
624
+ val origImg = if (cardWrap.childCount > 0) cardWrap.getChildAt(0) as? ImageView else null
574
625
  val bitmap = if (origImg != null) {
575
626
  // Extract the bitmap from the drawable
576
627
  val drawable = origImg.drawable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-morph-card",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",