react-native-morph-card 0.1.1 → 0.1.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.
@@ -225,23 +225,18 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
225
225
  }
226
226
 
227
227
  /**
228
- * Walk the view tree and hide any screen container that isn't the source screen.
229
- * This catches modal screens added to separate ScreenStacks.
228
+ * Walk the view tree and hide any screen container that isn't already known.
229
+ * This catches modal screens added to separate ScreenStacks (e.g. transparentModal).
230
230
  */
231
- private fun hideNewScreenContainers(root: ViewGroup, sourceScreen: View?) {
231
+ private fun hideNewScreenContainers(root: ViewGroup, knownScreens: Set<View>) {
232
232
  fun walk(group: ViewGroup) {
233
233
  val name = group.javaClass.name
234
234
  if (name.contains("ScreenStack") || name.contains("ScreenContainer")) {
235
235
  for (i in 0 until group.childCount) {
236
236
  val child = group.getChildAt(i)
237
- if (child !== sourceScreen && child.visibility == View.VISIBLE) {
238
- // Check if this is a screen container we haven't seen before
239
- val childScreen = child
240
- val isSourceAncestor = isAncestorOf(childScreen, sourceScreen)
241
- if (!isSourceAncestor) {
242
- childScreen.visibility = View.INVISIBLE
243
- Log.d(TAG, "preDraw: hid screen container ${childScreen.javaClass.simpleName}")
244
- }
237
+ if (!knownScreens.contains(child) && child.visibility == View.VISIBLE) {
238
+ child.visibility = View.INVISIBLE
239
+ Log.d(TAG, "preDraw: hid new screen ${child.javaClass.simpleName} in ${group.javaClass.simpleName}")
245
240
  }
246
241
  }
247
242
  }
@@ -253,14 +248,25 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
253
248
  walk(root)
254
249
  }
255
250
 
256
- private fun isAncestorOf(potentialAncestor: View, target: View?): Boolean {
257
- if (target == null) return false
258
- var current: View? = target
259
- while (current != null) {
260
- if (current === potentialAncestor) return true
261
- current = if (current.parent is View) current.parent as View else null
251
+ /**
252
+ * Collect all current children of ScreenStack/ScreenContainer views.
253
+ */
254
+ private fun collectExistingScreens(root: ViewGroup): Set<View> {
255
+ val screens = mutableSetOf<View>()
256
+ fun walk(group: ViewGroup) {
257
+ val name = group.javaClass.name
258
+ if (name.contains("ScreenStack") || name.contains("ScreenContainer")) {
259
+ for (i in 0 until group.childCount) {
260
+ screens.add(group.getChildAt(i))
261
+ }
262
+ }
263
+ for (i in 0 until group.childCount) {
264
+ val child = group.getChildAt(i)
265
+ if (child is ViewGroup) walk(child)
266
+ }
262
267
  }
263
- return false
268
+ walk(root)
269
+ return screens
264
270
  }
265
271
 
266
272
  private fun removeHierarchyListener() {
@@ -344,9 +350,10 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
344
350
  // Also install a pre-draw listener on the DecorView to catch modal screens
345
351
  // that are added to a different ScreenStack (e.g. transparentModal).
346
352
  // This fires before every frame draw, so we can hide screens before they render.
347
- val savedSourceScreen = sourceScreen
353
+ val knownScreens = collectExistingScreens(decorView)
354
+ Log.d(TAG, "prepareExpand: tracking ${knownScreens.size} existing screens")
348
355
  val pdListener = ViewTreeObserver.OnPreDrawListener {
349
- hideNewScreenContainers(decorView, savedSourceScreen)
356
+ hideNewScreenContainers(decorView, knownScreens)
350
357
  true
351
358
  }
352
359
  decorView.viewTreeObserver.addOnPreDrawListener(pdListener)
@@ -355,7 +362,18 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
355
362
  // Capture snapshot
356
363
  val cardImage = captureSnapshot()
357
364
 
358
- // Create overlay at source position
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.
368
+ 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
374
+ fullScreenOverlay.isClickable = true
375
+
376
+ // Create card overlay at source position (on top of screen capture)
359
377
  val bgColor = cardBgColor
360
378
  val wrapper = FrameLayout(context)
361
379
  wrapper.layoutParams = FrameLayout.LayoutParams(cardWidth.toInt(), cardHeight.toInt())
@@ -373,9 +391,11 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
373
391
  content.scaleType = ImageView.ScaleType.FIT_XY
374
392
  content.layoutParams = FrameLayout.LayoutParams(cardWidth.toInt(), cardHeight.toInt())
375
393
  wrapper.addView(content)
394
+ wrapper.tag = "morphCardWrapper"
395
+ fullScreenOverlay.addView(wrapper)
376
396
 
377
- decorView.addView(wrapper)
378
- overlayContainer = wrapper
397
+ decorView.addView(fullScreenOverlay)
398
+ overlayContainer = fullScreenOverlay
379
399
 
380
400
  // Hide source card — overlay covers it
381
401
  alpha = 0f
@@ -461,7 +481,9 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
461
481
  Log.d(TAG, "animateExpand: pendingTarget w=${pendingTargetWidth} h=${pendingTargetHeight} br=${pendingTargetBorderRadius}")
462
482
 
463
483
  val dur = duration.toLong()
464
- val content = if (wrapper.childCount > 0) wrapper.getChildAt(0) else null
484
+ // Find the card wrapper inside the full-screen overlay
485
+ val cardWrapper = wrapper.findViewWithTag<FrameLayout>("morphCardWrapper") ?: wrapper
486
+ val content = if (cardWrapper.childCount > 0) cardWrapper.getChildAt(0) else null
465
487
 
466
488
  // Compute content offset for wrapper mode
467
489
  val contentCx = if (hasWrapper && pendingContentCentered) (targetWidthPx - cardWidth) / 2f else 0f
@@ -479,13 +501,14 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
479
501
 
480
502
  animator.addUpdateListener { anim ->
481
503
  val t = anim.animatedValue as Float
482
- wrapper.x = lerp(cardLeft, targetLeft, t)
483
- wrapper.y = lerp(cardTop, targetTop, t)
484
- val lp = wrapper.layoutParams
504
+
505
+ cardWrapper.x = lerp(cardLeft, targetLeft, t)
506
+ cardWrapper.y = lerp(cardTop, targetTop, t)
507
+ val lp = cardWrapper.layoutParams
485
508
  lp.width = lerp(cardWidth, targetWidthPx, t).toInt()
486
509
  lp.height = lerp(cardHeight, targetHeightPx, t).toInt()
487
- wrapper.layoutParams = lp
488
- setRoundedCorners(wrapper, lerp(cardCornerRadiusPx, targetCornerRadiusPx, t))
510
+ cardWrapper.layoutParams = lp
511
+ setRoundedCorners(cardWrapper, lerp(cardCornerRadiusPx, targetCornerRadiusPx, t))
489
512
 
490
513
  if (content != null) {
491
514
  if (hasWrapper) {
@@ -562,8 +585,9 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
562
585
  return
563
586
  }
564
587
 
565
- // Get the bitmap from the overlay
566
- val origImg = if (overlay.childCount > 0) overlay.getChildAt(0) as? ImageView else null
588
+ // Get the bitmap from the card wrapper inside the overlay
589
+ val cardWrap = overlay.findViewWithTag<FrameLayout>("morphCardWrapper") ?: overlay
590
+ val origImg = if (cardWrap.childCount > 0) cardWrap.getChildAt(0) as? ImageView else null
567
591
  val bitmap = if (origImg != null) {
568
592
  // Extract the bitmap from the drawable
569
593
  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.1",
3
+ "version": "0.1.3",
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",