@capgo/capacitor-native-navigation 8.1.0 → 8.1.1

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.
@@ -112,6 +112,8 @@ public class NativeNavigationPlugin extends Plugin {
112
112
  private String activeTransitionDirection = "forward";
113
113
  private RectF activeZoomSourceFrame;
114
114
  private float activeZoomCornerRadius = 0f;
115
+ private Drawable activeTransitionRootBackground;
116
+ private boolean activeTransitionRootBackgroundCaptured = false;
115
117
  private final Map<Integer, String> menuActionIds = new HashMap<>();
116
118
  private final Map<Integer, String> menuActionTitles = new HashMap<>();
117
119
  private final Map<Integer, String> menuActionPlacements = new HashMap<>();
@@ -338,8 +340,11 @@ public class NativeNavigationPlugin extends Plugin {
338
340
 
339
341
  if (transitionSnapshot != null) {
340
342
  root.removeView(transitionSnapshot);
343
+ restoreTransitionRootBackground();
341
344
  }
342
345
 
346
+ int transitionSurface = transitionSurfaceColor(webView);
347
+ prepareTransitionRootBackground(root, transitionSurface);
343
348
  Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);
344
349
  webView.draw(new Canvas(bitmap));
345
350
  if (zoomSourceRect != null) {
@@ -348,6 +353,7 @@ public class NativeNavigationPlugin extends Plugin {
348
353
  }
349
354
  transitionSnapshot = new ImageView(getContext());
350
355
  transitionSnapshot.setImageBitmap(bitmap);
356
+ transitionSnapshot.setBackgroundColor(transitionSurface);
351
357
  transitionSnapshot.setScaleType(ImageView.ScaleType.FIT_XY);
352
358
  FrameLayout.LayoutParams params =
353
359
  activeZoomSourceFrame == null
@@ -415,8 +421,9 @@ public class NativeNavigationPlugin extends Plugin {
415
421
  snapshotEndTranslation = -width * 0.3f;
416
422
  }
417
423
 
424
+ boolean stationaryTransition = isStationaryTransition(direction);
418
425
  webView.setTranslationX(startTranslation);
419
- webView.setAlpha("none".equals(direction) ? 1f : 0.01f);
426
+ webView.setAlpha(stationaryTransition ? 1f : 0.01f);
420
427
  View snapshot = transitionSnapshot;
421
428
  JSObject event = transitionEvent(transitionId, direction, durationMs);
422
429
  Runnable finish = () -> {
@@ -424,6 +431,7 @@ public class NativeNavigationPlugin extends Plugin {
424
431
  if (root != null && transitionSnapshot != null) {
425
432
  root.removeView(transitionSnapshot);
426
433
  }
434
+ restoreTransitionRootBackground();
427
435
  transitionSnapshot = null;
428
436
  activeTransitionId = null;
429
437
  activeZoomSourceFrame = null;
@@ -443,7 +451,7 @@ public class NativeNavigationPlugin extends Plugin {
443
451
  snapshot
444
452
  .animate()
445
453
  .translationX(snapshotEndTranslation)
446
- .alpha("none".equals(direction) ? 0f : 0.75f)
454
+ .alpha(stationaryTransition ? 0f : 0.75f)
447
455
  .setDuration(durationMs)
448
456
  .withEndAction(finish)
449
457
  .start();
@@ -480,6 +488,7 @@ public class NativeNavigationPlugin extends Plugin {
480
488
  if (root != null && transitionSnapshot != null) {
481
489
  root.removeView(transitionSnapshot);
482
490
  }
491
+ restoreTransitionRootBackground();
483
492
  transitionSnapshot = null;
484
493
  activeTransitionId = null;
485
494
  activeZoomSourceFrame = null;
@@ -1498,6 +1507,52 @@ public class NativeNavigationPlugin extends Plugin {
1498
1507
  return event;
1499
1508
  }
1500
1509
 
1510
+ private boolean isStationaryTransition(String direction) {
1511
+ return "tab".equals(direction) || "root".equals(direction) || "none".equals(direction);
1512
+ }
1513
+
1514
+ private void prepareTransitionRootBackground(FrameLayout root, int surfaceColor) {
1515
+ activeTransitionRootBackground = root.getBackground();
1516
+ activeTransitionRootBackgroundCaptured = true;
1517
+ if (needsTransitionSurface(activeTransitionRootBackground)) {
1518
+ root.setBackgroundColor(surfaceColor);
1519
+ }
1520
+ }
1521
+
1522
+ private void restoreTransitionRootBackground() {
1523
+ if (!activeTransitionRootBackgroundCaptured) {
1524
+ return;
1525
+ }
1526
+ FrameLayout root = contentRoot();
1527
+ if (root != null) {
1528
+ root.setBackground(activeTransitionRootBackground);
1529
+ }
1530
+ activeTransitionRootBackground = null;
1531
+ activeTransitionRootBackgroundCaptured = false;
1532
+ }
1533
+
1534
+ private boolean needsTransitionSurface(Drawable background) {
1535
+ if (background == null) {
1536
+ return true;
1537
+ }
1538
+ if (background instanceof ColorDrawable) {
1539
+ return Color.alpha(((ColorDrawable) background).getColor()) < 255;
1540
+ }
1541
+ return false;
1542
+ }
1543
+
1544
+ private int transitionSurfaceColor(View webView) {
1545
+ Drawable background = webView.getBackground();
1546
+ if (background instanceof ColorDrawable) {
1547
+ int color = ((ColorDrawable) background).getColor();
1548
+ if (Color.alpha(color) > 0) {
1549
+ return withAlpha(color, 255);
1550
+ }
1551
+ }
1552
+ int fallback = isNightMode() ? Color.rgb(18, 18, 18) : Color.WHITE;
1553
+ return dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", fallback);
1554
+ }
1555
+
1501
1556
  private FrameLayout contentRoot() {
1502
1557
  Activity activity = getActivity();
1503
1558
  return activity == null ? null : activity.findViewById(android.R.id.content);
@@ -70,6 +70,10 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
70
70
  private var activeTransitionDirection = "forward"
71
71
  private var activeZoomSourceFrame: CGRect?
72
72
  private var activeZoomCornerRadius: CGFloat = 0
73
+ private weak var activeTransitionContainer: UIView?
74
+ private var activeTransitionContainerBackgroundColor: UIColor?
75
+ private var activeTransitionContainerWasOpaque = false
76
+ private var activeTransitionContainerBackgroundCaptured = false
73
77
 
74
78
  private var usesSystemLiquidGlass: Bool {
75
79
  if #available(iOS 26.0, *) {
@@ -232,9 +236,14 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
232
236
  let cornerRadius = CGFloat(call.getDouble("cornerRadius") ?? 0)
233
237
 
234
238
  self.transitionSnapshot?.removeFromSuperview()
239
+ self.restoreTransitionContainerBackground()
240
+ let transitionSurface = nativeNavigationFallbackBackground(for: webView).withAlphaComponent(1.0)
241
+ self.prepareTransitionContainerBackground(transitionContainer, surface: transitionSurface)
235
242
  let snapshot = self.transitionSnapshotView(from: webView, sourceRect: zoomSourceRect)
236
243
  snapshot.frame = zoomSourceFrame ?? webView.frame
237
244
  snapshot.autoresizingMask = zoomSourceFrame == nil ? [.flexibleWidth, .flexibleHeight] : []
245
+ snapshot.backgroundColor = transitionSurface
246
+ snapshot.isOpaque = true
238
247
  snapshot.layer.cornerRadius = cornerRadius
239
248
  snapshot.clipsToBounds = cornerRadius > 0
240
249
  transitionContainer.insertSubview(snapshot, aboveSubview: webView)
@@ -292,8 +301,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
292
301
  private func finishStandardTransition(_ transition: NativeNavigationTransitionContext) {
293
302
  let width = transition.webView.bounds.width
294
303
  let transforms = standardTransitionTransforms(direction: transition.direction, width: width)
304
+ let usesStationaryCrossfade = nativeNavigationUsesStationaryTransitionCrossfade(direction: transition.direction)
295
305
  transition.webView.transform = transforms.start
296
- transition.webView.alpha = transition.direction == "none" ? 1 : 0.01
306
+ transition.webView.alpha = usesStationaryCrossfade ? 1 : 0.01
297
307
 
298
308
  UIView.animate(
299
309
  withDuration: max(transition.duration, 0),
@@ -303,7 +313,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
303
313
  transition.webView.transform = .identity
304
314
  transition.webView.alpha = 1
305
315
  transition.snapshot?.transform = transforms.snapshotEnd
306
- transition.snapshot?.alpha = transition.direction == "none" ? 0 : 0.75
316
+ transition.snapshot?.alpha = usesStationaryCrossfade ? 0 : 0.75
307
317
  },
308
318
  completion: { _ in
309
319
  self.finishTransitionCleanup(transition)
@@ -407,6 +417,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
407
417
  transition.webView.layer.cornerRadius = 0
408
418
  transition.webView.clipsToBounds = false
409
419
  transitionSnapshot = nil
420
+ restoreTransitionContainerBackground()
410
421
  activeTransitionId = nil
411
422
  activeZoomSourceFrame = nil
412
423
  let event: [String: Any] = ["id": transition.id, "direction": transition.direction, "duration": transition.durationMs]
@@ -414,6 +425,29 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
414
425
  transition.resolve(event)
415
426
  }
416
427
 
428
+ private func prepareTransitionContainerBackground(_ container: UIView, surface: UIColor) {
429
+ activeTransitionContainer = container
430
+ activeTransitionContainerBackgroundColor = container.backgroundColor
431
+ activeTransitionContainerWasOpaque = container.isOpaque
432
+ activeTransitionContainerBackgroundCaptured = true
433
+ if nativeNavigationNeedsTransitionSurface(container.backgroundColor) {
434
+ container.backgroundColor = surface
435
+ container.isOpaque = true
436
+ }
437
+ }
438
+
439
+ private func restoreTransitionContainerBackground() {
440
+ guard activeTransitionContainerBackgroundCaptured else {
441
+ return
442
+ }
443
+ activeTransitionContainer?.backgroundColor = activeTransitionContainerBackgroundColor
444
+ activeTransitionContainer?.isOpaque = activeTransitionContainerWasOpaque
445
+ activeTransitionContainer = nil
446
+ activeTransitionContainerBackgroundColor = nil
447
+ activeTransitionContainerWasOpaque = false
448
+ activeTransitionContainerBackgroundCaptured = false
449
+ }
450
+
417
451
  @objc func getPluginVersion(_ call: CAPPluginCall) {
418
452
  call.resolve([
419
453
  "version": implementation.getPluginVersion()
@@ -1611,9 +1645,20 @@ final class NativeNavigationTabContentController: UIViewController {
1611
1645
  private func nativeNavigationFallbackBackground(for view: UIView) -> UIColor {
1612
1646
  if let color = view.backgroundColor,
1613
1647
  color.cgColor.alpha > 0 {
1614
- return color
1648
+ return color.withAlphaComponent(1)
1649
+ }
1650
+ return UIColor.systemBackground.withAlphaComponent(1.0)
1651
+ }
1652
+
1653
+ func nativeNavigationUsesStationaryTransitionCrossfade(direction: String) -> Bool {
1654
+ direction == "tab" || direction == "root" || direction == "none"
1655
+ }
1656
+
1657
+ private func nativeNavigationNeedsTransitionSurface(_ color: UIColor?) -> Bool {
1658
+ guard let color else {
1659
+ return true
1615
1660
  }
1616
- return .systemBackground
1661
+ return color.cgColor.alpha < 1
1617
1662
  }
1618
1663
 
1619
1664
  private func nativeNavigationSnapshotPlaceholder(for view: UIView) -> UIView {
@@ -89,4 +89,12 @@ class NativeNavigationTests: XCTestCase {
89
89
  XCTAssertNil(webView.superview)
90
90
  XCTAssertEqual(controller.view.superview, webView)
91
91
  }
92
+
93
+ func testStationaryTransitionsCrossfadeSnapshotsAway() {
94
+ XCTAssertTrue(nativeNavigationUsesStationaryTransitionCrossfade(direction: "tab"))
95
+ XCTAssertTrue(nativeNavigationUsesStationaryTransitionCrossfade(direction: "root"))
96
+ XCTAssertTrue(nativeNavigationUsesStationaryTransitionCrossfade(direction: "none"))
97
+ XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "forward"))
98
+ XCTAssertFalse(nativeNavigationUsesStationaryTransitionCrossfade(direction: "back"))
99
+ }
92
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.1.0",
3
+ "version": "8.1.1",
4
4
  "description": "Capacitor plugin for native navbar, tabbar, safe-area handling, and WebView snapshot transitions.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -53,11 +53,12 @@
53
53
  "prepublishOnly": "bun run build",
54
54
  "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
55
55
  "demo:readme": "bun scripts/generate-readme-demos.mjs",
56
- "example:build": "bun run build && cd example-app && bun install && bun run build",
56
+ "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
57
57
  "example:capgo:deploy": "bun run example:build && bun scripts/deploy-example-capgo.mjs",
58
58
  "example:sync": "bun run example:build && cd example-app && bunx cap sync",
59
59
  "example:ios": "bun run example:build && cd example-app && ([ -d ios ] || bunx cap add ios) && bunx cap sync ios && bunx cap run ios",
60
- "example:android": "bun run example:build && cd example-app && ([ -d android ] || bunx cap add android) && bunx cap sync android && bunx cap run android"
60
+ "example:android": "bun run example:build && cd example-app && ([ -d android ] || bunx cap add android) && bunx cap sync android && bunx cap run android",
61
+ "example:install": "cd example-app && bun install --frozen-lockfile"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@capacitor/android": "^8.0.0",