@capgo/capacitor-native-navigation 8.4.1 → 8.4.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.
package/README.md CHANGED
@@ -1,21 +1,12 @@
1
1
  # @capgo/capacitor-native-navigation
2
2
 
3
- <a href="https://capgo.app/">
4
- <img
5
- src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-native-navigation"
6
- alt="Capgo - Instant updates for Capacitor"
7
- />
8
- </a>
3
+ <a href="https://capgo.app/?ref=plugin_native_navigation"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-native-navigation" alt="Capgo - Instant updates for Capacitor" /></a>
9
4
 
10
5
  <div align="center">
11
- <h2>
12
- <a href="https://capgo.app/?ref=plugin_native_navigation">Get instant updates for your app with Capgo</a>
13
- </h2>
14
- <h2>
15
- <a href="https://capgo.app/consulting/?ref=plugin_native_navigation">
16
- Missing a feature? We can build the plugin for you
17
- </a>
18
- </h2>
6
+ <p><b>Capgo</b>: open-source live updates for Ionic and Capacitor apps. Ship OTA fixes and features instantly, without waiting for app store review.</p>
7
+ <h2><a href="https://capgo.app/register/?ref=plugin_native_navigation">➡️ Get started for free</a></h2>
8
+ <p>14-day unlimited free trial. No credit card required</p>
9
+ <p><a href="https://capgo.app/consulting/?ref=plugin_native_navigation">Missing a feature? We'll build the plugin for you 💪</a></p>
19
10
  </div>
20
11
 
21
12
  Native navbar, tabbar, safe-area handling, and WebView snapshot transitions for Capacitor apps. Your web framework keeps routing and page rendering while this plugin owns the platform surfaces users expect to feel native.
@@ -105,6 +105,8 @@ public class NativeNavigationPlugin extends Plugin {
105
105
  private final Map<Integer, String> menuActionPlacements = new HashMap<>();
106
106
  private final List<NativeTabItem> tabItems = new ArrayList<>();
107
107
  private int selectedTabIndex = 0;
108
+ private boolean tabbarDisableIndicator = false;
109
+ private Integer tabbarIndicatorColor = null;
108
110
 
109
111
  @Override
110
112
  public void load() {
@@ -244,6 +246,8 @@ public class NativeNavigationPlugin extends Plugin {
244
246
  tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
245
247
  badgeBackgroundColor = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", Color.rgb(255, 59, 48));
246
248
  badgeTextColor = colorOption(call, colors, "badgeTextColor", "badgeText", Color.WHITE);
249
+ tabbarDisableIndicator = call.getBoolean("disableIndicator", false);
250
+ tabbarIndicatorColor = colorOption(call, colors, "indicatorColor", "indicator", null);
247
251
 
248
252
  for (int sourceIndex = 0; sourceIndex < tabs.length(); sourceIndex++) {
249
253
  JSONObject tab = tabs.optJSONObject(sourceIndex);
@@ -644,6 +648,68 @@ public class NativeNavigationPlugin extends Plugin {
644
648
  };
645
649
  }
646
650
 
651
+ private void applyTabbarContainerOutline() {
652
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || tabbarContainer == null) {
653
+ return;
654
+ }
655
+
656
+ if (tabbarStyle.isCurve()) {
657
+ tabbarContainer.setClipToOutline(false);
658
+ tabbarContainer.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
659
+ if (tabbarGlassBackdrop != null) {
660
+ tabbarGlassBackdrop.setClipToOutline(false);
661
+ tabbarGlassBackdrop.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
662
+ }
663
+ if (tabbarGlassSurface != null) {
664
+ tabbarGlassSurface.setClipToOutline(false);
665
+ tabbarGlassSurface.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
666
+ }
667
+ return;
668
+ }
669
+
670
+ ViewOutlineProvider floatingOutlineProvider = floatingTabbarOutlineProvider();
671
+ tabbarContainer.setClipToOutline(false);
672
+ tabbarContainer.setOutlineProvider(floatingOutlineProvider);
673
+ if (tabbarGlassBackdrop != null) {
674
+ tabbarGlassBackdrop.setClipToOutline(true);
675
+ tabbarGlassBackdrop.setOutlineProvider(floatingOutlineProvider);
676
+ }
677
+ if (tabbarGlassSurface != null) {
678
+ tabbarGlassSurface.setClipToOutline(true);
679
+ tabbarGlassSurface.setOutlineProvider(floatingOutlineProvider);
680
+ }
681
+ }
682
+
683
+ private ViewOutlineProvider floatingTabbarOutlineProvider() {
684
+ return new ViewOutlineProvider() {
685
+ @Override
686
+ public void getOutline(View view, Outline outline) {
687
+ int width = view.getWidth();
688
+ int height = view.getHeight();
689
+ if (width <= 0 || height <= 0 || tabbar == null) {
690
+ outline.setEmpty();
691
+ return;
692
+ }
693
+
694
+ Path path = tabbar.chromeBackgroundPath(width, height);
695
+ if (TabbarChromeSupport.canApplyPathOutline()) {
696
+ outline.setPath(path);
697
+ return;
698
+ }
699
+
700
+ boolean hasDetachedTrailing = tabItems.stream().anyMatch((item) -> item.detachedTrailing);
701
+ int capsuleWidth = TabbarChromeSupport.floatingCapsuleWidth(
702
+ width,
703
+ dp(tabbarStyle.height),
704
+ dp(10),
705
+ hasDetachedTrailing
706
+ );
707
+ float radius = Math.min(dp(tabbarStyle.cornerRadius), height / 2f);
708
+ outline.setRoundRect(0, 0, capsuleWidth, height, radius);
709
+ }
710
+ };
711
+ }
712
+
647
713
  private Toolbar ensureToolbar() {
648
714
  if (toolbar != null) {
649
715
  return toolbar;
@@ -698,7 +764,6 @@ public class NativeNavigationPlugin extends Plugin {
698
764
  tabbarGlassSurface = new View(getContext());
699
765
  tabbarGlassBackdrop.setVisibility(View.GONE);
700
766
  tabbarGlassSurface.setVisibility(View.GONE);
701
-
702
767
  tabbar = new NativeTabbarLayout(getContext());
703
768
  tabbar.setClipChildren(false);
704
769
  tabbar.setClipToPadding(false);
@@ -722,6 +787,7 @@ public class NativeNavigationPlugin extends Plugin {
722
787
  new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_TABBAR_DP))
723
788
  );
724
789
  }
790
+ applyTabbarContainerOutline();
725
791
  return tabbar;
726
792
  }
727
793
 
@@ -788,10 +854,10 @@ public class NativeNavigationPlugin extends Plugin {
788
854
  button.addView(centerFill, new FrameLayout.LayoutParams(centerFillDiameter, centerFillDiameter, Gravity.CENTER));
789
855
  }
790
856
 
791
- if (!center && selected) {
857
+ if (!center && selected && !tabbarDisableIndicator) {
792
858
  GradientDrawable selectedBackground = new GradientDrawable();
793
859
  selectedBackground.setShape(GradientDrawable.OVAL);
794
- selectedBackground.setColor(withAlpha(tintColor, 34));
860
+ selectedBackground.setColor(TabbarChromeSupport.resolveSelectedIndicatorColor(tabbarIndicatorColor, tintColor));
795
861
  View selectedCircle = new View(getContext());
796
862
  selectedCircle.setBackground(selectedBackground);
797
863
  button.addView(selectedCircle, new FrameLayout.LayoutParams(dp(58), dp(58), Gravity.CENTER));
@@ -1245,25 +1311,21 @@ public class NativeNavigationPlugin extends Plugin {
1245
1311
  );
1246
1312
  }
1247
1313
 
1314
+ Path chromeBackgroundPath(int width, int height) {
1315
+ return backgroundPath(width, height);
1316
+ }
1317
+
1248
1318
  private Path backgroundPath(int width, int height) {
1249
1319
  Path path = new Path();
1250
1320
  if (!style.isCurve()) {
1251
- float radius = dp(style.cornerRadius);
1252
- int capsuleWidth = capsuleWidth(width);
1253
- path.addRoundRect(new RectF(0, 0, capsuleWidth, height), radius, radius, Path.Direction.CW);
1254
- int trailingIndex = detachedTrailingIndex();
1255
- if (trailingIndex >= 0) {
1256
- float diameter = dp(style.height);
1257
- float left = width - diameter;
1258
- float top = (height - diameter) / 2f;
1259
- path.addRoundRect(
1260
- new RectF(left, top, left + diameter, top + diameter),
1261
- diameter / 2f,
1262
- diameter / 2f,
1263
- Path.Direction.CW
1264
- );
1265
- }
1266
- return path;
1321
+ return TabbarChromeSupport.buildFloatingTabbarPath(
1322
+ width,
1323
+ height,
1324
+ dp(style.cornerRadius),
1325
+ dp(style.height),
1326
+ dp(10),
1327
+ detachedTrailingIndex() >= 0
1328
+ );
1267
1329
  }
1268
1330
  float barTop = dp(style.barTop());
1269
1331
  float barHeight = dp(style.height);
@@ -1607,6 +1669,7 @@ public class NativeNavigationPlugin extends Plugin {
1607
1669
  }
1608
1670
 
1609
1671
  tabbar.setTabbarStyle(tabbarStyle, drawColor, centerIndex);
1672
+ applyTabbarContainerOutline();
1610
1673
  }
1611
1674
 
1612
1675
  private int resolvedTabbarSurfaceColor() {
@@ -1815,6 +1878,15 @@ public class NativeNavigationPlugin extends Plugin {
1815
1878
  tabbarContainer.setLayoutParams(tabbarContainerParams);
1816
1879
  fillContainer(tabbarGlassBackdrop);
1817
1880
  fillContainer(tabbarGlassSurface);
1881
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1882
+ tabbarContainer.invalidateOutline();
1883
+ if (tabbarGlassBackdrop != null) {
1884
+ tabbarGlassBackdrop.invalidateOutline();
1885
+ }
1886
+ if (tabbarGlassSurface != null) {
1887
+ tabbarGlassSurface.invalidateOutline();
1888
+ }
1889
+ }
1818
1890
  }
1819
1891
 
1820
1892
  if (tabbar != null) {
@@ -0,0 +1,66 @@
1
+ package app.capgo.nativenavigation;
2
+
3
+ import android.graphics.Path;
4
+ import android.graphics.RectF;
5
+ import android.os.Build;
6
+
7
+ public final class TabbarChromeSupport {
8
+
9
+ public enum TabbarOutlineMode {
10
+ FLOATING_SHAPE,
11
+ CURVE_DEFAULT
12
+ }
13
+
14
+ private TabbarChromeSupport() {}
15
+
16
+ public static int resolveSelectedIndicatorColor(Integer explicitColor, int tintColor) {
17
+ if (explicitColor != null) {
18
+ return explicitColor;
19
+ }
20
+ return (34 << 24) | (tintColor & 0x00FFFFFF);
21
+ }
22
+
23
+ public static TabbarOutlineMode outlineModeForShape(String shape) {
24
+ return "curve".equals(shape) ? TabbarOutlineMode.CURVE_DEFAULT : TabbarOutlineMode.FLOATING_SHAPE;
25
+ }
26
+
27
+ public static boolean shouldClipGlassToOutline(TabbarOutlineMode mode) {
28
+ return mode == TabbarOutlineMode.FLOATING_SHAPE;
29
+ }
30
+
31
+ public static int floatingCapsuleWidth(int totalWidth, int barHeightPx, int trailingGapPx, boolean hasDetachedTrailing) {
32
+ if (!hasDetachedTrailing) {
33
+ return totalWidth;
34
+ }
35
+ return Math.max(0, totalWidth - barHeightPx - trailingGapPx);
36
+ }
37
+
38
+ public static Path buildFloatingTabbarPath(
39
+ int width,
40
+ int height,
41
+ float cornerRadiusPx,
42
+ float barHeightPx,
43
+ float trailingGapPx,
44
+ boolean hasDetachedTrailing
45
+ ) {
46
+ Path path = new Path();
47
+ int capsuleWidth = floatingCapsuleWidth(width, (int) barHeightPx, (int) trailingGapPx, hasDetachedTrailing);
48
+ path.addRoundRect(new RectF(0, 0, capsuleWidth, height), cornerRadiusPx, cornerRadiusPx, Path.Direction.CW);
49
+ if (hasDetachedTrailing) {
50
+ float diameter = barHeightPx;
51
+ float left = width - diameter;
52
+ float top = (height - diameter) / 2f;
53
+ path.addRoundRect(
54
+ new RectF(left, top, left + diameter, top + diameter),
55
+ diameter / 2f,
56
+ diameter / 2f,
57
+ Path.Direction.CW
58
+ );
59
+ }
60
+ return path;
61
+ }
62
+
63
+ public static boolean canApplyPathOutline() {
64
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
65
+ }
66
+ }
@@ -70,6 +70,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
70
70
  private var tabSelectedImages: [UIImage?] = []
71
71
  private var suppressTabSelectEvent = false
72
72
  private var tabSelectEmittedForReTap = false
73
+ private var lastSetTabbarOptions: [String: Any] = [:]
73
74
  private var transitionSnapshot: UIView?
74
75
  private var activeTransitionId: String?
75
76
  private var activeTransitionDirection = "forward"
@@ -234,6 +235,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
234
235
  icons: icons
235
236
  )
236
237
  self.applySystemTabBarItems(items, selectedIndex: selectedIndex, animated: call.getBool("animated", false))
238
+ if let options = call.options as? [String: Any] {
239
+ self.lastSetTabbarOptions = options
240
+ }
237
241
  self.applyTabBarAppearance(tabBar: tabBar, options: call)
238
242
  if items.isEmpty {
239
243
  self.tabbarVisible = false
@@ -622,17 +626,29 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
622
626
  tabBarController?.view.layoutIfNeeded()
623
627
 
624
628
  if let tabBar = tabBar {
625
- tabBar.isTranslucent = !prefersOpaqueTabBarBackground()
626
- let standardAppearance = tabBar.standardAppearance
627
- tabBar.standardAppearance = standardAppearance
628
- if #available(iOS 15.0, *) {
629
- let scrollEdgeAppearance = tabBar.scrollEdgeAppearance ?? standardAppearance
630
- tabBar.scrollEdgeAppearance = scrollEdgeAppearance
631
- }
632
- tabBar.items?.forEach { item in
633
- item.standardAppearance = standardAppearance
629
+ if usesSystemLiquidGlass,
630
+ !lastSetTabbarOptions.isEmpty,
631
+ let refreshCall = CAPPluginCall(
632
+ callbackId: "tabbar-refresh",
633
+ methodName: "setTabbar",
634
+ options: lastSetTabbarOptions,
635
+ success: { _, _ in },
636
+ error: { _ in }
637
+ ) {
638
+ applyTabBarAppearance(tabBar: tabBar, options: refreshCall)
639
+ } else {
640
+ tabBar.isTranslucent = !prefersOpaqueTabBarBackground()
641
+ let standardAppearance = tabBar.standardAppearance
642
+ tabBar.standardAppearance = standardAppearance
634
643
  if #available(iOS 15.0, *) {
635
- item.scrollEdgeAppearance = tabBar.scrollEdgeAppearance
644
+ let scrollEdgeAppearance = tabBar.scrollEdgeAppearance ?? standardAppearance
645
+ tabBar.scrollEdgeAppearance = scrollEdgeAppearance
646
+ }
647
+ tabBar.items?.forEach { item in
648
+ item.standardAppearance = standardAppearance
649
+ if #available(iOS 15.0, *) {
650
+ item.scrollEdgeAppearance = tabBar.scrollEdgeAppearance
651
+ }
636
652
  }
637
653
  }
638
654
  tabBar.setNeedsLayout()
@@ -1480,7 +1496,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1480
1496
  private func applyTabBarAppearance(tabBar: UITabBar, options call: CAPPluginCall) {
1481
1497
  if usesSystemLiquidGlass {
1482
1498
  let standardAppearance = UITabBarAppearance()
1483
- configureSystemTabBarStandardBackground(standardAppearance)
1499
+ // Match scroll-edge background so UIKit does not swap Liquid Glass for
1500
+ // legacy material when content scrolls under the bar (iOS 26+).
1501
+ configureSystemTabBarScrollEdgeBackground(standardAppearance, options: call)
1484
1502
  applyTabBarColorOptions(standardAppearance, tabBar: tabBar, options: call)
1485
1503
  applyTabBarBadgeOptions(standardAppearance, options: call)
1486
1504
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.4.1",
3
+ "version": "8.4.3",
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",